From df09375be394484ac6756d81290d65d7ffddb260 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 17 Nov 2019 21:38:57 +0100 Subject: [PATCH 01/51] webauthn --- lam-packaging/debian/copyright | 28 +++++++++++++ lam/copyright | 28 +++++++++++++ lam/graphics/webauthn.svg | 31 ++++++++++++++ lam/lib/2factor.inc | 74 +++++++++++++++++++++++++++++++++- 4 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 lam/graphics/webauthn.svg diff --git a/lam-packaging/debian/copyright b/lam-packaging/debian/copyright index ca07053c..cafeac9d 100644 --- a/lam-packaging/debian/copyright +++ b/lam-packaging/debian/copyright @@ -415,6 +415,33 @@ E: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +F: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Programs and licenses with other licenses and/or authors than the main license and authors: @@ -438,6 +465,7 @@ templates/lib/extra/cropperjs B 2018 Chen Fengyuan style/600_cropper*.css B 2018 Chen Fengyuan templates/lib/extra/duo/*.js E 2019 Duo Security lib/3rdParty/duo/*.php E 2019 Duo Security +graphics/webauthn.svg F 2017 Duo Security, Inc. templates/lib/600_jquery.magnific-popup.js B 2016 Dmitry Semenov style/610_magnific-popup.css B 2016 Dmitry Semenov style/responsive/105_normalize.css B Nicolas Gallagher and Jonathan Neal diff --git a/lam/copyright b/lam/copyright index 0640e706..d2a46ec4 100644 --- a/lam/copyright +++ b/lam/copyright @@ -414,6 +414,33 @@ E: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +F: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Programs and licenses with other licenses and/or authors than the main license and authors: @@ -437,6 +464,7 @@ templates/lib/extra/cropperjs B 2018 Chen Fengyuan style/600_cropper*.css B 2018 Chen Fengyuan templates/lib/extra/duo/*.js E 2019 Duo Security lib/3rdParty/duo/*.php E 2019 Duo Security +graphics/webauthn.svg F 2017 Duo Security, Inc. templates/lib/600_jquery.magnific-popup.js B 2016 Dmitry Semenov style/610_magnific-popup.css B 2016 Dmitry Semenov style/responsive/105_normalize.css B Nicolas Gallagher and Jonathan Neal diff --git a/lam/graphics/webauthn.svg b/lam/graphics/webauthn.svg new file mode 100644 index 00000000..d0bd06f4 --- /dev/null +++ b/lam/graphics/webauthn.svg @@ -0,0 +1,31 @@ + + + + shield + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index 55267b31..38733342 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -3,8 +3,8 @@ namespace LAM\LIB\TWO_FACTOR; use \selfServiceProfile; use \LAMConfig; use \htmlScript; -use \htmlInputField; use \htmlIframe; +use \htmlImage; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) @@ -464,6 +464,76 @@ class DuoProvider extends BaseProvider { } +/** + * Provider for Webauthn. + */ +class WebauthnProvider extends BaseProvider { + + /** + * Constructor. + * + * @param TwoFactorConfiguration $config configuration + */ + public function __construct(&$config) { + $this->config = $config; + } + + /** + * {@inheritDoc} + * @see \LAM\LIB\TWO_FACTOR\TwoFactorProvider::getSerials() + */ + public function getSerials($user, $password) { + return array('WEBAUTHN'); + } + + /** + * {@inheritDoc} + * @see \LAM\LIB\TWO_FACTOR\TwoFactorProvider::isShowSubmitButton() + */ + public function isShowSubmitButton() { + return false; + } + + /** + * {@inheritDoc} + * @see \LAM\LIB\TWO_FACTOR\TwoFactorProvider::hasCustomInputForm() + */ + public function hasCustomInputForm() { + return true; + } + + /** + * {@inheritDoc} + * @see \LAM\LIB\TWO_FACTOR\BaseProvider::addCustomInput() + */ + public function addCustomInput(&$row, $userDn) { + $pathPrefix = $this->config->isSelfService ? '../' : ''; + $row->add(new htmlImage($pathPrefix . '../graphics/webauthn.svg'), 12); + } + + /** + * {@inheritDoc} + * @see \LAM\LIB\TWO_FACTOR\TwoFactorProvider::verify2ndFactor() + */ + public function verify2ndFactor($user, $password, $serial, $twoFactorInput) { + logNewMessage(LOG_DEBUG, 'PrivacyIDEAProvider: Checking 2nd factor for ' . $user); + $loginAttribute = $this->getLoginAttributeValue($user); + $response = $_POST['sig_response']; + include_once(__DIR__ . "/3rdParty/duo/Web.php"); + $result = \Duo\Web::verifyResponse( + $this->config->twoFactorAuthenticationClientId, + $this->config->twoFactorAuthenticationSecretKey, + $this->getAKey(), + $response); + if ($result === $loginAttribute) { + return true; + } + logNewMessage(LOG_ERR, 'DUO authentication failed'); + return false; + } + +} + /** * Returns the correct 2 factor provider. */ @@ -477,6 +547,8 @@ class TwoFactorProviderService { const TWO_FACTOR_YUBICO = 'yubico'; /** 2factor authentication via DUO */ const TWO_FACTOR_DUO = 'duo'; + /** 2factor authentication via webauthn */ + const TWO_FACTOR_WEBAUTHN = 'webauthn'; private $config; From 62dcd743fb8478c8c19f10e26ad3b1982a3963be Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 17 Nov 2019 21:51:24 +0100 Subject: [PATCH 02/51] webauthn --- lam/lib/2factor.inc | 3 +++ lam/templates/config/confmain.php | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index 1b2d0a3b..65619772 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -593,6 +593,9 @@ class TwoFactorProviderService { elseif ($this->config->twoFactorAuthentication == TwoFactorProviderService::TWO_FACTOR_DUO) { return new DuoProvider($this->config); } + elseif ($this->config->twoFactorAuthentication == TwoFactorProviderService::TWO_FACTOR_WEBAUTHN) { + return new WebauthnProvider($this->config); + } throw new \Exception('Invalid provider: ' . $this->config->twoFactorAuthentication); } diff --git a/lam/templates/config/confmain.php b/lam/templates/config/confmain.php index 6a506aeb..a927b591 100644 --- a/lam/templates/config/confmain.php +++ b/lam/templates/config/confmain.php @@ -466,6 +466,8 @@ if (extension_loaded('curl')) { 'privacyIDEA' => TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA, 'YubiKey' => TwoFactorProviderService::TWO_FACTOR_YUBICO, 'Duo' => TwoFactorProviderService::TWO_FACTOR_DUO, + 'Webauthn' => TwoFactorProviderService::TWO_FACTOR_DUO, + 'Webauthn' => TwoFactorProviderService::TWO_FACTOR_WEBAUTHN, ); $twoFactorSelect = new htmlResponsiveSelect('twoFactor', $twoFactorOptions, array($conf->getTwoFactorAuthentication()), _('Provider'), '514'); $twoFactorSelect->setHasDescriptiveElements(true); @@ -474,7 +476,9 @@ if (extension_loaded('curl')) { 'twoFactorOptional', 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey', 'twoFactorAttribute'), TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA => array('twoFactorURLs', 'twoFactorClientId', 'twoFactorSecretKey'), TwoFactorProviderService::TWO_FACTOR_YUBICO => array('twoFactorURL', 'twoFactorAttribute'), - TwoFactorProviderService::TWO_FACTOR_DUO => array('twoFactorURLs', 'twoFactorOptional', 'twoFactorInsecure'), + TwoFactorProviderService::TWO_FACTOR_DUO => array('twoFactorURLs', 'twoFactorOptional', 'twoFactorInsecure', 'twoFactorLabel'), + TwoFactorProviderService::TWO_FACTOR_WEBAUTHN => array('twoFactorURL', 'twoFactorURLs', 'twoFactorInsecure', 'twoFactorLabel', + 'twoFactorOptional', 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey', 'twoFactorAttribute'), )); $twoFactorSelect->setTableRowsToShow(array( TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA => array('twoFactorURL', 'twoFactorInsecure', 'twoFactorLabel', From ffd47f8ca51ac6cfafa11b1b225d08996372fa6f Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 21 Nov 2019 19:34:01 +0100 Subject: [PATCH 03/51] webauthn --- lam/lib/2factor.inc | 14 +++++++++++ lam/templates/lib/500_lam.js | 46 ++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index 65619772..a4a3f976 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -5,6 +5,10 @@ use \LAMConfig; use \htmlScript; use \htmlIframe; use \htmlImage; +use \htmlButton; +use \htmlJavaScript; +use \htmlStatusMessage; +use \htmlDiv; use \LAMException; /* @@ -519,6 +523,16 @@ class WebauthnProvider extends BaseProvider { public function addCustomInput(&$row, $userDn) { $pathPrefix = $this->config->isSelfService ? '../' : ''; $row->add(new htmlImage($pathPrefix . '../graphics/webauthn.svg'), 12); + $row->addVerticalSpacer('1rem'); + $registerButton = new htmlButton('register_webauthn', _('Register new key')); + $registerButton->setCSSClasses(array('fullwidth hidden')); + $row->add($registerButton, 12); + $loginButton = new htmlButton('login_webauthn', _('Login')); + $loginButton->setCSSClasses(array('fullwidth hidden')); + $row->add($loginButton, 12); + $errorMessage = new htmlStatusMessage('ERROR', '', _('This service requires a browser with webauthn support.')); + $row->add(new htmlDiv(null, $errorMessage, array('hidden webauthn-error')), 12); + $row->add(new htmlJavaScript('window.lam.webauthn.start(\'' . $pathPrefix . '\');'), 0); } /** diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index efd7251e..b6c17635 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -1356,6 +1356,52 @@ window.lam.selfservice.addMultiValue = function(fieldNamePrefix, addButton) { jQuery(addButton).remove(); }; +window.lam.webauthn = window.lam.webauthn || {}; + +/** + * Starts the webauthn process. + * + * @param prefix path prefix for Ajax endpoint + */ +window.lam.webauthn.start = function(prefix) { + jQuery(document).ready( + function() { + window.lam.webauthn.run(prefix); + } + ); +} + +/** + * Checks if the user is registered and starts login/registration. + * + * @param prefix path prefix for Ajax endpoint + */ +window.lam.webauthn.run = function(prefix) { + var token = jQuery('#sec_token').val(); + // check for webauthn support + if (!navigator.credentials || (typeof(PublicKeyCredential) === "undefined")) { + jQuery('.webauthn-error').show(); + return; + } + + var data = { + action: 'status', + sec_token: token + }; + jQuery.ajax({ + url: prefix + 'misc/ajax.php?function=webauthn', + method: 'POST', + data: data + }) + .done(function(jsonData) { + console.log(jsonData); + }) + .fail(function() { + console.log('Webauthn failed'); + }); +} + + jQuery(document).ready(function() { window.lam.gui.equalHeight(); window.lam.form.autoTrim(); From deca797a80641dbbdf50f3e634d99340f2498551 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 21 Nov 2019 21:48:14 +0100 Subject: [PATCH 04/51] PHP 7 --- lam/HISTORY | 1 + lam/docs/manual-sources/chapter-installation.xml | 2 +- lam/docs/manual-sources/overview.xml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lam/HISTORY b/lam/HISTORY index f7b14b47..bdf00fe5 100644 --- a/lam/HISTORY +++ b/lam/HISTORY @@ -1,4 +1,5 @@ December 2019 7.0 + - PHP 7 required - Lamdaemon can be configured with directory prefix for homedirs - Account list filters match on substrings instead of whole value - YubiKey: support to configure multiple verification servers diff --git a/lam/docs/manual-sources/chapter-installation.xml b/lam/docs/manual-sources/chapter-installation.xml index 5528d3ce..e801ba62 100644 --- a/lam/docs/manual-sources/chapter-installation.xml +++ b/lam/docs/manual-sources/chapter-installation.xml @@ -15,7 +15,7 @@ Apache/Nginx webserver (SSL recommended) with PHP module (PHP - (>= 5.6.0) with ldap, gettext, xml, openssl and optional + (>= 7.0.0) with ldap, gettext, xml, openssl and optional OpenSSL) diff --git a/lam/docs/manual-sources/overview.xml b/lam/docs/manual-sources/overview.xml index fb88a123..68980042 100644 --- a/lam/docs/manual-sources/overview.xml +++ b/lam/docs/manual-sources/overview.xml @@ -63,7 +63,7 @@ - PHP (>= 5.6.0) + PHP (>= 7.0.0) From 38045cbac1f72ac8c94d72756c838f3f0d5944b2 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 21 Nov 2019 21:51:05 +0100 Subject: [PATCH 05/51] PHP 7 --- lam/lib/checkEnvironment.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lam/lib/checkEnvironment.inc b/lam/lib/checkEnvironment.inc index 352e00e5..b8c9f11a 100644 --- a/lam/lib/checkEnvironment.inc +++ b/lam/lib/checkEnvironment.inc @@ -36,7 +36,7 @@ include_once(__DIR__ . "/../lib/status.inc"); include_once(__DIR__ . "/../lib/config.inc"); // check if PHP >= 5.6.0 -if (version_compare(phpversion(), '5.6.0') < 0) { +if (version_compare(phpversion(), '7.0.0') < 0) { echo "\n\n"; echo "\n\n"; echo "\n"; @@ -46,7 +46,7 @@ if (version_compare(phpversion(), '5.6.0') < 0) { echo "\n"; echo "LDAP Account Manager\n"; echo "\n"; - StatusMessage("ERROR", "LAM needs a PHP 5 version which is greater or equal than 5.6.0.", "Please upgrade your PHP installation. The found version is " . phpversion()); + StatusMessage("ERROR", "LAM needs a PHP 5 version which is greater or equal than 7.0.0.", "Please upgrade your PHP installation. The found version is " . phpversion()); echo "

"; echo ""; exit(); From db48e32dc686021e8334167e84079e25ea06ebd8 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 21 Nov 2019 22:02:38 +0100 Subject: [PATCH 06/51] fixed message --- lam/lib/account.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lam/lib/account.inc b/lam/lib/account.inc index 25a04091..8efb0240 100644 --- a/lam/lib/account.inc +++ b/lam/lib/account.inc @@ -1637,8 +1637,8 @@ function enforceUserIsLoggedIn($check2ndFactor = true) { die(); } if ($check2ndFactor && isset($_SESSION['2factorRequired'])) { - die(); logNewMessage(LOG_WARNING, 'Detected unauthorized access to page that requires login (2nd factor not provided): ' . $_SERVER["SCRIPT_FILENAME"]); + die(); } } From e329c28c3eab93d3894fffa4b1c3686927afbe00 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 21 Nov 2019 22:03:42 +0100 Subject: [PATCH 07/51] webauthn --- lam/composer.json | 8 ++++++ lam/lib/webauthn.inc | 49 ++++++++++++++++++++++++++++++++++ lam/templates/lib/500_lam.js | 1 + lam/templates/login2Factor.php | 4 ++- lam/templates/misc/ajax.php | 12 +++++++++ 5 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 lam/composer.json create mode 100644 lam/lib/webauthn.inc diff --git a/lam/composer.json b/lam/composer.json new file mode 100644 index 00000000..a78d3bba --- /dev/null +++ b/lam/composer.json @@ -0,0 +1,8 @@ +{ + "config": { + "vendor-dir": "lib/3rdParty/composer" + }, + "require" : { + "web-auth/webauthn-lib" : "2.1.7" + } +} \ No newline at end of file diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc new file mode 100644 index 00000000..8d105fea --- /dev/null +++ b/lam/lib/webauthn.inc @@ -0,0 +1,49 @@ +getTwoFactorAuthenticationCaption(); diff --git a/lam/templates/misc/ajax.php b/lam/templates/misc/ajax.php index eff89675..09383d35 100644 --- a/lam/templates/misc/ajax.php +++ b/lam/templates/misc/ajax.php @@ -101,6 +101,11 @@ class Ajax { $this->checkPasswordStrength($jsonInput); die(); } + if ($function === 'webauthn') { + enforceUserIsLoggedIn(false); + $this->manageWebauthn(); + die(); + } enforceUserIsLoggedIn(); if ($function == 'passwordChange') { $this->managePasswordChange($jsonInput); @@ -177,6 +182,13 @@ class Ajax { echo json_encode(array("result" => $result)); } + /** + * Manages webauthn requests. + */ + private function manageWebauthn() { + $userDN = $_SESSION['ldap']->getUserName(); + } + /** * Handles DN selection fields. * From 9637c2dff6aba6a8a2a1f51ed43651cab22ae477 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 24 Nov 2019 09:45:57 +0100 Subject: [PATCH 08/51] webauthn --- lam-packaging/debian/copyright | 87 +- lam/composer.lock | 901 ++++++ lam/copyright | 87 +- lam/lib/3rdParty/composer/autoload.php | 7 + .../3rdParty/composer/beberlei/assert/LICENSE | 11 + .../composer/beberlei/assert/composer.json | 63 + .../beberlei/assert/lib/Assert/Assert.php | 96 + .../beberlei/assert/lib/Assert/Assertion.php | 2825 +++++++++++++++++ .../assert/lib/Assert/AssertionChain.php | 254 ++ .../lib/Assert/AssertionFailedException.php | 35 + .../lib/Assert/InvalidArgumentException.php | 76 + .../assert/lib/Assert/LazyAssertion.php | 230 ++ .../lib/Assert/LazyAssertionException.php | 55 + .../beberlei/assert/lib/Assert/functions.php | 80 + .../beberlei/assert/phpstan-code.neon | 10 + .../beberlei/assert/phpstan-tests.neon | 10 + .../composer/composer/ClassLoader.php | 445 +++ lam/lib/3rdParty/composer/composer/LICENSE | 56 + .../composer/composer/autoload_classmap.php | 9 + .../composer/composer/autoload_files.php | 11 + .../composer/composer/autoload_namespaces.php | 9 + .../composer/composer/autoload_psr4.php | 22 + .../composer/composer/autoload_real.php | 70 + .../composer/composer/autoload_static.php | 124 + .../3rdParty/composer/composer/installed.json | 915 ++++++ .../composer/fgrosse/phpasn1/CHANGELOG.md | 38 + .../3rdParty/composer/fgrosse/phpasn1/LICENSE | 19 + .../composer/fgrosse/phpasn1/README.md | 167 + .../composer/fgrosse/phpasn1/composer.json | 47 + .../fgrosse/phpasn1/lib/ASN1/ASNObject.php | 355 +++ .../phpasn1/lib/ASN1/AbstractString.php | 136 + .../fgrosse/phpasn1/lib/ASN1/AbstractTime.php | 78 + .../fgrosse/phpasn1/lib/ASN1/Base128.php | 63 + .../ASN1/Composite/AttributeTypeAndValue.php | 35 + .../phpasn1/lib/ASN1/Composite/RDNString.php | 37 + .../Composite/RelativeDistinguishedName.php | 50 + .../fgrosse/phpasn1/lib/ASN1/Construct.php | 191 ++ .../Exception/NotImplementedException.php | 15 + .../lib/ASN1/Exception/ParserException.php | 29 + .../lib/ASN1/ExplicitlyTaggedObject.php | 131 + .../fgrosse/phpasn1/lib/ASN1/Identifier.php | 339 ++ .../composer/fgrosse/phpasn1/lib/ASN1/OID.php | 198 ++ .../fgrosse/phpasn1/lib/ASN1/Parsable.php | 32 + .../phpasn1/lib/ASN1/TemplateParser.php | 70 + .../phpasn1/lib/ASN1/Universal/BMPString.php | 41 + .../phpasn1/lib/ASN1/Universal/BitString.php | 88 + .../phpasn1/lib/ASN1/Universal/Boolean.php | 75 + .../lib/ASN1/Universal/CharacterString.php | 28 + .../phpasn1/lib/ASN1/Universal/Enumerated.php | 21 + .../lib/ASN1/Universal/GeneralString.php | 34 + .../lib/ASN1/Universal/GeneralizedTime.php | 134 + .../lib/ASN1/Universal/GraphicString.php | 34 + .../phpasn1/lib/ASN1/Universal/IA5String.php | 35 + .../phpasn1/lib/ASN1/Universal/Integer.php | 130 + .../phpasn1/lib/ASN1/Universal/NullObject.php | 54 + .../lib/ASN1/Universal/NumericString.php | 38 + .../lib/ASN1/Universal/ObjectDescriptor.php | 26 + .../lib/ASN1/Universal/ObjectIdentifier.php | 138 + .../lib/ASN1/Universal/OctetString.php | 91 + .../lib/ASN1/Universal/PrintableString.php | 53 + .../Universal/RelativeObjectIdentifier.php | 57 + .../phpasn1/lib/ASN1/Universal/Sequence.php | 23 + .../phpasn1/lib/ASN1/Universal/Set.php | 21 + .../phpasn1/lib/ASN1/Universal/T61String.php | 36 + .../phpasn1/lib/ASN1/Universal/UTCTime.php | 77 + .../phpasn1/lib/ASN1/Universal/UTF8String.php | 34 + .../lib/ASN1/Universal/UniversalString.php | 36 + .../lib/ASN1/Universal/VisibleString.php | 34 + .../lib/ASN1/UnknownConstructedObject.php | 59 + .../phpasn1/lib/ASN1/UnknownObject.php | 59 + .../phpasn1/lib/Utility/BigInteger.php | 197 ++ .../phpasn1/lib/Utility/BigIntegerBcmath.php | 133 + .../phpasn1/lib/Utility/BigIntegerGmp.php | 133 + .../phpasn1/lib/X509/AlgorithmIdentifier.php | 22 + .../phpasn1/lib/X509/CSR/Attributes.php | 68 + .../fgrosse/phpasn1/lib/X509/CSR/CSR.php | 137 + .../lib/X509/CertificateExtensions.php | 100 + .../phpasn1/lib/X509/CertificateSubject.php | 108 + .../fgrosse/phpasn1/lib/X509/PrivateKey.php | 35 + .../fgrosse/phpasn1/lib/X509/PublicKey.php | 35 + .../fgrosse/phpasn1/lib/X509/SAN/DNSName.php | 28 + .../phpasn1/lib/X509/SAN/IPAddress.php | 73 + .../lib/X509/SAN/SubjectAlternativeNames.php | 96 + .../composer/nyholm/psr7/CHANGELOG.md | 85 + lam/lib/3rdParty/composer/nyholm/psr7/LICENSE | 21 + .../3rdParty/composer/nyholm/psr7/README.md | 111 + .../composer/nyholm/psr7/composer.json | 51 + .../psr7/src/Factory/HttplugFactory.php | 40 + .../nyholm/psr7/src/Factory/Psr17Factory.php | 73 + .../composer/nyholm/psr7/src/MessageTrait.php | 202 ++ .../composer/nyholm/psr7/src/Request.php | 45 + .../composer/nyholm/psr7/src/RequestTrait.php | 113 + .../composer/nyholm/psr7/src/Response.php | 88 + .../nyholm/psr7/src/ServerRequest.php | 162 + .../composer/nyholm/psr7/src/Stream.php | 257 ++ .../composer/nyholm/psr7/src/UploadedFile.php | 171 + .../3rdParty/composer/nyholm/psr7/src/Uri.php | 310 ++ .../composer/paragonie/random_compat/LICENSE | 22 + .../paragonie/random_compat/build-phar.sh | 5 + .../paragonie/random_compat/composer.json | 34 + .../dist/random_compat.phar.pubkey | 5 + .../dist/random_compat.phar.pubkey.asc | 11 + .../paragonie/random_compat/lib/random.php | 32 + .../random_compat/other/build_phar.php | 57 + .../random_compat/psalm-autoload.php | 9 + .../paragonie/random_compat/psalm.xml | 19 + .../php-http/message-factory/CHANGELOG.md | 65 + .../composer/php-http/message-factory/LICENSE | 19 + .../php-http/message-factory/README.md | 36 + .../php-http/message-factory/composer.json | 27 + .../php-http/message-factory/puli.json | 43 + .../message-factory/src/MessageFactory.php | 12 + .../message-factory/src/RequestFactory.php | 34 + .../message-factory/src/ResponseFactory.php | 35 + .../message-factory/src/StreamFactory.php | 25 + .../message-factory/src/UriFactory.php | 24 + .../composer/psr/http-client/CHANGELOG.md | 19 + .../3rdParty/composer/psr/http-client/LICENSE | 19 + .../composer/psr/http-client/README.md | 14 + .../composer/psr/http-client/composer.json | 27 + .../src/ClientExceptionInterface.php | 10 + .../psr/http-client/src/ClientInterface.php | 20 + .../src/NetworkExceptionInterface.php | 24 + .../src/RequestExceptionInterface.php | 24 + .../composer/psr/http-factory/.gitignore | 2 + .../psr/http-factory/.pullapprove.yml | 7 + .../composer/psr/http-factory/LICENSE | 21 + .../composer/psr/http-factory/README.md | 10 + .../composer/psr/http-factory/composer.json | 35 + .../src/RequestFactoryInterface.php | 18 + .../src/ResponseFactoryInterface.php | 18 + .../src/ServerRequestFactoryInterface.php | 24 + .../src/StreamFactoryInterface.php | 45 + .../src/UploadedFileFactoryInterface.php | 34 + .../http-factory/src/UriFactoryInterface.php | 17 + .../composer/psr/http-message/CHANGELOG.md | 36 + .../composer/psr/http-message/LICENSE | 19 + .../composer/psr/http-message/README.md | 13 + .../composer/psr/http-message/composer.json | 26 + .../psr/http-message/src/MessageInterface.php | 187 ++ .../psr/http-message/src/RequestInterface.php | 129 + .../http-message/src/ResponseInterface.php | 68 + .../src/ServerRequestInterface.php | 261 ++ .../psr/http-message/src/StreamInterface.php | 158 + .../src/UploadedFileInterface.php | 123 + .../psr/http-message/src/UriInterface.php | 323 ++ .../composer/ramsey/uuid/CHANGELOG.md | 376 +++ .../composer/ramsey/uuid/CODE_OF_CONDUCT.md | 74 + .../composer/ramsey/uuid/CONTRIBUTING.md | 75 + lam/lib/3rdParty/composer/ramsey/uuid/LICENSE | 19 + .../3rdParty/composer/ramsey/uuid/README.md | 159 + .../composer/ramsey/uuid/composer.json | 80 + .../composer/ramsey/uuid/src/BinaryUtils.php | 43 + .../uuid/src/Builder/DefaultUuidBuilder.php | 54 + .../uuid/src/Builder/DegradedUuidBuilder.php | 53 + .../uuid/src/Builder/UuidBuilderInterface.php | 34 + .../ramsey/uuid/src/Codec/CodecInterface.php | 58 + .../ramsey/uuid/src/Codec/GuidStringCodec.php | 102 + .../uuid/src/Codec/OrderedTimeCodec.php | 68 + .../ramsey/uuid/src/Codec/StringCodec.php | 170 + .../src/Codec/TimestampFirstCombCodec.php | 107 + .../uuid/src/Codec/TimestampLastCombCodec.php | 23 + .../Converter/Number/BigNumberConverter.php | 54 + .../Number/DegradedNumberConverter.php | 58 + .../Converter/NumberConverterInterface.php | 46 + .../Converter/Time/BigNumberTimeConverter.php | 58 + .../Converter/Time/DegradedTimeConverter.php | 42 + .../src/Converter/Time/PhpTimeConverter.php | 47 + .../src/Converter/TimeConverterInterface.php | 35 + .../composer/ramsey/uuid/src/DegradedUuid.php | 114 + .../Exception/InvalidUuidStringException.php | 22 + .../UnsatisfiedDependencyException.php | 23 + .../UnsupportedOperationException.php | 22 + .../composer/ramsey/uuid/src/FeatureSet.php | 333 ++ .../uuid/src/Generator/CombGenerator.php | 88 + .../src/Generator/DefaultTimeGenerator.php | 138 + .../uuid/src/Generator/MtRandGenerator.php | 41 + .../uuid/src/Generator/OpenSslGenerator.php | 38 + .../src/Generator/PeclUuidRandomGenerator.php | 37 + .../src/Generator/PeclUuidTimeGenerator.php | 38 + .../src/Generator/RandomBytesGenerator.php | 37 + .../src/Generator/RandomGeneratorFactory.php | 31 + .../Generator/RandomGeneratorInterface.php | 33 + .../uuid/src/Generator/RandomLibAdapter.php | 62 + .../src/Generator/SodiumRandomGenerator.php | 36 + .../src/Generator/TimeGeneratorFactory.php | 72 + .../src/Generator/TimeGeneratorInterface.php | 39 + .../Provider/Node/FallbackNodeProvider.php | 58 + .../src/Provider/Node/RandomNodeProvider.php | 42 + .../src/Provider/Node/SystemNodeProvider.php | 125 + .../src/Provider/NodeProviderInterface.php | 30 + .../src/Provider/Time/FixedTimeProvider.php | 76 + .../src/Provider/Time/SystemTimeProvider.php | 33 + .../src/Provider/TimeProviderInterface.php | 29 + .../composer/ramsey/uuid/src/Uuid.php | 740 +++++ .../composer/ramsey/uuid/src/UuidFactory.php | 314 ++ .../ramsey/uuid/src/UuidFactoryInterface.php | 103 + .../ramsey/uuid/src/UuidInterface.php | 270 ++ .../spomky-labs/base64url/.php_cs.dist | 61 + .../composer/spomky-labs/base64url/LICENSE | 22 + .../spomky-labs/base64url/composer.json | 31 + .../spomky-labs/base64url/src/Base64Url.php | 50 + .../cbor-php/.github/CONTRIBUTING.md | 25 + .../spomky-labs/cbor-php/.github/FUNDING.yml | 1 + .../cbor-php/.github/ISSUE_TEMPLATE.md | 16 + .../cbor-php/.github/PULL_REQUEST_TEMPLATE.md | 17 + .../spomky-labs/cbor-php/.php_cs.dist | 61 + .../spomky-labs/cbor-php/CODE_OF_CONDUCT.md | 46 + .../composer/spomky-labs/cbor-php/LICENSE | 21 + .../spomky-labs/cbor-php/composer.json | 50 + .../spomky-labs/cbor-php/infection.json.dist | 11 + .../spomky-labs/cbor-php/phpstan.neon | 11 + .../cbor-php/src/AbstractCBORObject.php | 48 + .../cbor-php/src/ByteStringObject.php | 64 + .../src/ByteStringWithChunkObject.php | 87 + .../spomky-labs/cbor-php/src/CBORObject.php | 28 + .../spomky-labs/cbor-php/src/Decoder.php | 159 + .../cbor-php/src/InfiniteListObject.php | 69 + .../cbor-php/src/InfiniteMapObject.php | 73 + .../cbor-php/src/LengthCalculator.php | 66 + .../spomky-labs/cbor-php/src/ListObject.php | 93 + .../spomky-labs/cbor-php/src/MapItem.php | 43 + .../spomky-labs/cbor-php/src/MapObject.php | 88 + .../spomky-labs/cbor-php/src/OtherObject.php | 47 + .../cbor-php/src/OtherObject/BreakObject.php | 39 + .../DoublePrecisionFloatObject.php | 100 + .../cbor-php/src/OtherObject/FalseObject.php | 39 + .../src/OtherObject/GenericObject.php | 34 + .../OtherObject/HalfPrecisionFloatObject.php | 89 + .../cbor-php/src/OtherObject/NullObject.php | 38 + .../src/OtherObject/OtherObjectManager.php | 48 + .../cbor-php/src/OtherObject/SimpleObject.php | 54 + .../SinglePrecisionFloatObject.php | 89 + .../cbor-php/src/OtherObject/TrueObject.php | 39 + .../src/OtherObject/UndefinedObject.php | 39 + .../cbor-php/src/SignedIntegerObject.php | 113 + .../spomky-labs/cbor-php/src/Stream.php | 19 + .../spomky-labs/cbor-php/src/StringStream.php | 58 + .../cbor-php/src/Tag/Base16EncodingTag.php | 57 + .../cbor-php/src/Tag/Base64EncodingTag.php | 62 + .../cbor-php/src/Tag/Base64UrlEncodingTag.php | 58 + .../cbor-php/src/Tag/BigFloatTag.php | 100 + .../cbor-php/src/Tag/DecimalFractionTag.php | 95 + .../spomky-labs/cbor-php/src/Tag/EpochTag.php | 44 + .../cbor-php/src/Tag/GenericTag.php | 35 + .../src/Tag/NegativeBigIntegerTag.php | 56 + .../src/Tag/PositiveBigIntegerTag.php | 54 + .../cbor-php/src/Tag/TagObjectManager.php | 53 + .../cbor-php/src/Tag/TimestampTag.php | 61 + .../spomky-labs/cbor-php/src/TagObject.php | 56 + .../cbor-php/src/TextStringObject.php | 64 + .../src/TextStringWithChunkObject.php | 87 + .../cbor-php/src/UnsignedIntegerObject.php | 116 + .../composer/symfony/polyfill-ctype/Ctype.php | 227 ++ .../composer/symfony/polyfill-ctype/LICENSE | 19 + .../composer/symfony/polyfill-ctype/README.md | 12 + .../symfony/polyfill-ctype/bootstrap.php | 26 + .../symfony/polyfill-ctype/composer.json | 34 + .../composer/web-auth/cose-lib/LICENSE | 21 + .../composer/web-auth/cose-lib/README.md | 31 + .../composer/web-auth/cose-lib/composer.json | 45 + .../cose-lib/src/Algorithm/Algorithm.php | 19 + .../cose-lib/src/Algorithm/Mac/HS256.php | 34 + .../src/Algorithm/Mac/HS256Truncated64.php | 34 + .../cose-lib/src/Algorithm/Mac/HS384.php | 34 + .../cose-lib/src/Algorithm/Mac/HS512.php | 34 + .../cose-lib/src/Algorithm/Mac/Hmac.php | 43 + .../cose-lib/src/Algorithm/Mac/Mac.php | 24 + .../cose-lib/src/Algorithm/Manager.php | 63 + .../cose-lib/src/Algorithm/ManagerFactory.php | 50 + .../src/Algorithm/Signature/ECDSA/ECDSA.php | 58 + .../Algorithm/Signature/ECDSA/ECSignature.php | 133 + .../src/Algorithm/Signature/ECDSA/ES256.php | 60 + .../src/Algorithm/Signature/ECDSA/ES256K.php | 60 + .../src/Algorithm/Signature/ECDSA/ES384.php | 60 + .../src/Algorithm/Signature/ECDSA/ES512.php | 60 + .../src/Algorithm/Signature/EdDSA/ED256.php | 40 + .../src/Algorithm/Signature/EdDSA/ED512.php | 40 + .../src/Algorithm/Signature/EdDSA/Ed25519.php | 24 + .../src/Algorithm/Signature/EdDSA/EdDSA.php | 65 + .../src/Algorithm/Signature/RSA/PS256.php | 31 + .../src/Algorithm/Signature/RSA/PS384.php | 31 + .../src/Algorithm/Signature/RSA/PS512.php | 31 + .../src/Algorithm/Signature/RSA/PSSRSA.php | 182 ++ .../src/Algorithm/Signature/RSA/RS1.php | 29 + .../src/Algorithm/Signature/RSA/RS256.php | 29 + .../src/Algorithm/Signature/RSA/RS384.php | 29 + .../src/Algorithm/Signature/RSA/RS512.php | 29 + .../src/Algorithm/Signature/RSA/RSA.php | 47 + .../src/Algorithm/Signature/Signature.php | 24 + .../web-auth/cose-lib/src/Algorithms.php | 157 + .../web-auth/cose-lib/src/Key/Ec2Key.php | 144 + .../web-auth/cose-lib/src/Key/Key.php | 90 + .../web-auth/cose-lib/src/Key/OkpKey.php | 66 + .../web-auth/cose-lib/src/Key/RsaKey.php | 197 ++ .../cose-lib/src/Key/SymmetricKey.php | 33 + .../web-auth/cose-lib/src/Verifier.php | 18 + .../web-auth/metadata-service/LICENSE | 21 + .../web-auth/metadata-service/README.md | 31 + .../web-auth/metadata-service/composer.json | 39 + .../src/AuthenticatorStatus.php | 33 + .../src/BiometricAccuracyDescriptor.php | 94 + .../src/BiometricStatusReport.php | 101 + .../src/CodeAccuracyDescriptor.php | 68 + .../DisplayPNGCharacteristicsDescriptor.php | 124 + .../src/DistantSingleMetadata.php | 60 + .../src/DistantSingleMetadataFactory.php | 43 + .../metadata-service/src/EcdaaTrustAnchor.php | 90 + .../src/ExtensionDescriptor.php | 68 + .../metadata-service/src/MetadataService.php | 84 + .../src/MetadataServiceFactory.php | 43 + .../src/MetadataStatement.php | 489 +++ .../src/MetadataStatementFetcher.php | 79 + .../src/MetadataStatementRepository.php | 19 + .../src/MetadataTOCPayload.php | 76 + .../src/MetadataTOCPayloadEntry.php | 145 + .../src/PatternAccuracyDescriptor.php | 57 + .../metadata-service/src/RgbPaletteEntry.php | 57 + .../metadata-service/src/RogueListEntry.php | 45 + .../src/SimpleMetadataStatementRepository.php | 138 + .../metadata-service/src/SingleMetadata.php | 54 + .../metadata-service/src/StatusReport.php | 114 + .../src/VerificationMethodANDCombinations.php | 44 + .../src/VerificationMethodDescriptor.php | 80 + .../web-auth/metadata-service/src/Version.php | 46 + .../composer/web-auth/webauthn-lib/LICENSE | 21 + .../composer/web-auth/webauthn-lib/README.md | 31 + .../web-auth/webauthn-lib/composer.json | 59 + .../AndroidKeyAttestationStatementSupport.php | 161 + ...idSafetyNetAttestationStatementSupport.php | 252 ++ .../AttestationObject.php | 54 + .../AttestationObjectLoader.php | 98 + .../AttestationStatement.php | 140 + .../AttestationStatementSupport.php | 25 + .../AttestationStatementSupportManager.php | 41 + .../FidoU2FAttestationStatementSupport.php | 134 + .../NoneAttestationStatementSupport.php | 38 + .../PackedAttestationStatementSupport.php | 204 ++ .../TPMAttestationStatementSupport.php | 325 ++ .../src/AttestedCredentialData.php | 98 + .../AuthenticationExtension.php | 59 + .../AuthenticationExtensionsClientInputs.php | 75 + .../AuthenticationExtensionsClientOutputs.php | 83 + ...nticationExtensionsClientOutputsLoader.php | 34 + .../ExtensionOutputChecker.php | 22 + .../ExtensionOutputCheckerHandler.php | 37 + .../ExtensionOutputError.php | 36 + .../src/AuthenticatorAssertionResponse.php | 67 + ...uthenticatorAssertionResponseValidator.php | 205 ++ .../src/AuthenticatorAttestationResponse.php | 38 + ...henticatorAttestationResponseValidator.php | 152 + .../webauthn-lib/src/AuthenticatorData.php | 124 + .../src/AuthenticatorResponse.php | 35 + .../src/AuthenticatorSelectionCriteria.php | 96 + .../webauthn-lib/src/CertificateToolbox.php | 209 ++ .../webauthn-lib/src/CollectedClientData.php | 138 + .../web-auth/webauthn-lib/src/Credential.php | 46 + .../webauthn-lib/src/PublicKeyCredential.php | 65 + .../PublicKeyCredentialCreationOptions.php | 177 ++ .../src/PublicKeyCredentialDescriptor.php | 105 + ...ublicKeyCredentialDescriptorCollection.php | 82 + .../src/PublicKeyCredentialEntity.php | 57 + .../src/PublicKeyCredentialLoader.php | 130 + .../src/PublicKeyCredentialOptions.php | 61 + .../src/PublicKeyCredentialParameters.php | 76 + .../src/PublicKeyCredentialRequestOptions.php | 127 + .../src/PublicKeyCredentialRpEntity.php | 56 + .../src/PublicKeyCredentialSource.php | 233 ++ .../PublicKeyCredentialSourceRepository.php | 26 + .../src/PublicKeyCredentialUserEntity.php | 80 + .../web-auth/webauthn-lib/src/Server.php | 264 ++ .../webauthn-lib/src/StringStream.php | 72 + .../IgnoreTokenBindingHandler.php | 24 + .../src/TokenBinding/TokenBinding.php | 77 + .../src/TokenBinding/TokenBindingHandler.php | 21 + .../TokenBindingNotSupportedHandler.php | 25 + .../src/TrustPath/CertificateTrustPath.php | 55 + .../src/TrustPath/EcdaaKeyIdTrustPath.php | 49 + .../src/TrustPath/EmptyTrustPath.php | 29 + .../webauthn-lib/src/TrustPath/TrustPath.php | 21 + .../src/TrustPath/TrustPathLoader.php | 48 + .../src/Util/CoseSignatureFixer.php | 54 + lam/lib/webauthn.inc | 80 +- lam/templates/config/confmain.php | 5 +- lam/templates/lib/500_lam.js | 30 + lam/templates/misc/ajax.php | 25 +- 386 files changed, 33146 insertions(+), 60 deletions(-) create mode 100644 lam/composer.lock create mode 100644 lam/lib/3rdParty/composer/autoload.php create mode 100644 lam/lib/3rdParty/composer/beberlei/assert/LICENSE create mode 100644 lam/lib/3rdParty/composer/beberlei/assert/composer.json create mode 100644 lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/Assert.php create mode 100644 lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/Assertion.php create mode 100644 lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/AssertionChain.php create mode 100644 lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/AssertionFailedException.php create mode 100644 lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/InvalidArgumentException.php create mode 100644 lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/LazyAssertion.php create mode 100644 lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/LazyAssertionException.php create mode 100644 lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/functions.php create mode 100644 lam/lib/3rdParty/composer/beberlei/assert/phpstan-code.neon create mode 100644 lam/lib/3rdParty/composer/beberlei/assert/phpstan-tests.neon create mode 100644 lam/lib/3rdParty/composer/composer/ClassLoader.php create mode 100644 lam/lib/3rdParty/composer/composer/LICENSE create mode 100644 lam/lib/3rdParty/composer/composer/autoload_classmap.php create mode 100644 lam/lib/3rdParty/composer/composer/autoload_files.php create mode 100644 lam/lib/3rdParty/composer/composer/autoload_namespaces.php create mode 100644 lam/lib/3rdParty/composer/composer/autoload_psr4.php create mode 100644 lam/lib/3rdParty/composer/composer/autoload_real.php create mode 100644 lam/lib/3rdParty/composer/composer/autoload_static.php create mode 100644 lam/lib/3rdParty/composer/composer/installed.json create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/CHANGELOG.md create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/LICENSE create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/README.md create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/composer.json create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/ASNObject.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/AbstractString.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/AbstractTime.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Base128.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Composite/AttributeTypeAndValue.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Composite/RDNString.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Composite/RelativeDistinguishedName.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Construct.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Exception/NotImplementedException.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Exception/ParserException.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/ExplicitlyTaggedObject.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Identifier.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/OID.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Parsable.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/TemplateParser.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/BMPString.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/BitString.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Boolean.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/CharacterString.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Enumerated.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/GeneralString.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/GeneralizedTime.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/GraphicString.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/IA5String.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Integer.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/NullObject.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/NumericString.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/ObjectDescriptor.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/ObjectIdentifier.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/OctetString.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/PrintableString.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/RelativeObjectIdentifier.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Sequence.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Set.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/T61String.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/UTCTime.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/UTF8String.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/UniversalString.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/VisibleString.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/UnknownConstructedObject.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/UnknownObject.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/Utility/BigInteger.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/Utility/BigIntegerBcmath.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/Utility/BigIntegerGmp.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/AlgorithmIdentifier.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CSR/Attributes.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CSR/CSR.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CertificateExtensions.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CertificateSubject.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/PrivateKey.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/PublicKey.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/SAN/DNSName.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/SAN/IPAddress.php create mode 100644 lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/SAN/SubjectAlternativeNames.php create mode 100644 lam/lib/3rdParty/composer/nyholm/psr7/CHANGELOG.md create mode 100644 lam/lib/3rdParty/composer/nyholm/psr7/LICENSE create mode 100644 lam/lib/3rdParty/composer/nyholm/psr7/README.md create mode 100644 lam/lib/3rdParty/composer/nyholm/psr7/composer.json create mode 100644 lam/lib/3rdParty/composer/nyholm/psr7/src/Factory/HttplugFactory.php create mode 100644 lam/lib/3rdParty/composer/nyholm/psr7/src/Factory/Psr17Factory.php create mode 100644 lam/lib/3rdParty/composer/nyholm/psr7/src/MessageTrait.php create mode 100644 lam/lib/3rdParty/composer/nyholm/psr7/src/Request.php create mode 100644 lam/lib/3rdParty/composer/nyholm/psr7/src/RequestTrait.php create mode 100644 lam/lib/3rdParty/composer/nyholm/psr7/src/Response.php create mode 100644 lam/lib/3rdParty/composer/nyholm/psr7/src/ServerRequest.php create mode 100644 lam/lib/3rdParty/composer/nyholm/psr7/src/Stream.php create mode 100644 lam/lib/3rdParty/composer/nyholm/psr7/src/UploadedFile.php create mode 100644 lam/lib/3rdParty/composer/nyholm/psr7/src/Uri.php create mode 100644 lam/lib/3rdParty/composer/paragonie/random_compat/LICENSE create mode 100755 lam/lib/3rdParty/composer/paragonie/random_compat/build-phar.sh create mode 100644 lam/lib/3rdParty/composer/paragonie/random_compat/composer.json create mode 100644 lam/lib/3rdParty/composer/paragonie/random_compat/dist/random_compat.phar.pubkey create mode 100644 lam/lib/3rdParty/composer/paragonie/random_compat/dist/random_compat.phar.pubkey.asc create mode 100644 lam/lib/3rdParty/composer/paragonie/random_compat/lib/random.php create mode 100644 lam/lib/3rdParty/composer/paragonie/random_compat/other/build_phar.php create mode 100644 lam/lib/3rdParty/composer/paragonie/random_compat/psalm-autoload.php create mode 100644 lam/lib/3rdParty/composer/paragonie/random_compat/psalm.xml create mode 100644 lam/lib/3rdParty/composer/php-http/message-factory/CHANGELOG.md create mode 100644 lam/lib/3rdParty/composer/php-http/message-factory/LICENSE create mode 100644 lam/lib/3rdParty/composer/php-http/message-factory/README.md create mode 100644 lam/lib/3rdParty/composer/php-http/message-factory/composer.json create mode 100644 lam/lib/3rdParty/composer/php-http/message-factory/puli.json create mode 100644 lam/lib/3rdParty/composer/php-http/message-factory/src/MessageFactory.php create mode 100644 lam/lib/3rdParty/composer/php-http/message-factory/src/RequestFactory.php create mode 100644 lam/lib/3rdParty/composer/php-http/message-factory/src/ResponseFactory.php create mode 100644 lam/lib/3rdParty/composer/php-http/message-factory/src/StreamFactory.php create mode 100644 lam/lib/3rdParty/composer/php-http/message-factory/src/UriFactory.php create mode 100644 lam/lib/3rdParty/composer/psr/http-client/CHANGELOG.md create mode 100644 lam/lib/3rdParty/composer/psr/http-client/LICENSE create mode 100644 lam/lib/3rdParty/composer/psr/http-client/README.md create mode 100644 lam/lib/3rdParty/composer/psr/http-client/composer.json create mode 100644 lam/lib/3rdParty/composer/psr/http-client/src/ClientExceptionInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-client/src/ClientInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-client/src/NetworkExceptionInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-client/src/RequestExceptionInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-factory/.gitignore create mode 100644 lam/lib/3rdParty/composer/psr/http-factory/.pullapprove.yml create mode 100644 lam/lib/3rdParty/composer/psr/http-factory/LICENSE create mode 100644 lam/lib/3rdParty/composer/psr/http-factory/README.md create mode 100644 lam/lib/3rdParty/composer/psr/http-factory/composer.json create mode 100644 lam/lib/3rdParty/composer/psr/http-factory/src/RequestFactoryInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-factory/src/ResponseFactoryInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-factory/src/ServerRequestFactoryInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-factory/src/StreamFactoryInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-factory/src/UploadedFileFactoryInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-factory/src/UriFactoryInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-message/CHANGELOG.md create mode 100644 lam/lib/3rdParty/composer/psr/http-message/LICENSE create mode 100644 lam/lib/3rdParty/composer/psr/http-message/README.md create mode 100644 lam/lib/3rdParty/composer/psr/http-message/composer.json create mode 100644 lam/lib/3rdParty/composer/psr/http-message/src/MessageInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-message/src/RequestInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-message/src/ResponseInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-message/src/ServerRequestInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-message/src/StreamInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-message/src/UploadedFileInterface.php create mode 100644 lam/lib/3rdParty/composer/psr/http-message/src/UriInterface.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/CHANGELOG.md create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/CODE_OF_CONDUCT.md create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/CONTRIBUTING.md create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/LICENSE create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/README.md create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/composer.json create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/BinaryUtils.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Builder/DefaultUuidBuilder.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Builder/DegradedUuidBuilder.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Builder/UuidBuilderInterface.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/CodecInterface.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/GuidStringCodec.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/OrderedTimeCodec.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/StringCodec.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/TimestampLastCombCodec.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Number/BigNumberConverter.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/NumberConverterInterface.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/TimeConverterInterface.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/DegradedUuid.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Exception/InvalidUuidStringException.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Exception/UnsatisfiedDependencyException.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Exception/UnsupportedOperationException.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/FeatureSet.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/CombGenerator.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/DefaultTimeGenerator.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/MtRandGenerator.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/OpenSslGenerator.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomBytesGenerator.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomGeneratorFactory.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomGeneratorInterface.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomLibAdapter.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/SodiumRandomGenerator.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/TimeGeneratorFactory.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/TimeGeneratorInterface.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/NodeProviderInterface.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/TimeProviderInterface.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/Uuid.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/UuidFactory.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/UuidFactoryInterface.php create mode 100644 lam/lib/3rdParty/composer/ramsey/uuid/src/UuidInterface.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/base64url/.php_cs.dist create mode 100644 lam/lib/3rdParty/composer/spomky-labs/base64url/LICENSE create mode 100644 lam/lib/3rdParty/composer/spomky-labs/base64url/composer.json create mode 100644 lam/lib/3rdParty/composer/spomky-labs/base64url/src/Base64Url.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/.github/CONTRIBUTING.md create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/.github/FUNDING.yml create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/.github/ISSUE_TEMPLATE.md create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/.php_cs.dist create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/CODE_OF_CONDUCT.md create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/LICENSE create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/composer.json create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/infection.json.dist create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/phpstan.neon create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/AbstractCBORObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/ByteStringObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/ByteStringWithChunkObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/CBORObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Decoder.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/InfiniteListObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/InfiniteMapObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/LengthCalculator.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/ListObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/MapItem.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/MapObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/BreakObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/DoublePrecisionFloatObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/FalseObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/GenericObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/HalfPrecisionFloatObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/NullObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/OtherObjectManager.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/SimpleObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/SinglePrecisionFloatObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/TrueObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/UndefinedObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/SignedIntegerObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Stream.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/StringStream.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/Base16EncodingTag.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/Base64EncodingTag.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/Base64UrlEncodingTag.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/BigFloatTag.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/DecimalFractionTag.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/EpochTag.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/GenericTag.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/NegativeBigIntegerTag.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/PositiveBigIntegerTag.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/TagObjectManager.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/TimestampTag.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/TagObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/TextStringObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/TextStringWithChunkObject.php create mode 100644 lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/UnsignedIntegerObject.php create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-ctype/Ctype.php create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-ctype/LICENSE create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-ctype/README.md create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-ctype/bootstrap.php create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-ctype/composer.json create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/LICENSE create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/README.md create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/composer.json create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Algorithm.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Mac/HS256.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Mac/HS256Truncated64.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Mac/HS384.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Mac/HS512.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Mac/Hmac.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Mac/Mac.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Manager.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/ManagerFactory.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECDSA.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECSignature.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256K.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES384.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES512.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/ED256.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/ED512.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed25519.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/EdDSA.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS256.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS384.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS512.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/RSA/PSSRSA.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS1.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS256.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS384.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS512.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/RSA/RSA.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/Signature.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithms.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/Ec2Key.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/Key.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/OkpKey.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/RsaKey.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/SymmetricKey.php create mode 100644 lam/lib/3rdParty/composer/web-auth/cose-lib/src/Verifier.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/LICENSE create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/README.md create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/composer.json create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/AuthenticatorStatus.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/BiometricAccuracyDescriptor.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/BiometricStatusReport.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/CodeAccuracyDescriptor.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/DisplayPNGCharacteristicsDescriptor.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/DistantSingleMetadata.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/DistantSingleMetadataFactory.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/EcdaaTrustAnchor.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/ExtensionDescriptor.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataService.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataServiceFactory.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataStatement.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataStatementFetcher.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataStatementRepository.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataTOCPayload.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataTOCPayloadEntry.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/PatternAccuracyDescriptor.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/RgbPaletteEntry.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/RogueListEntry.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/SimpleMetadataStatementRepository.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/SingleMetadata.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/StatusReport.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/VerificationMethodANDCombinations.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/VerificationMethodDescriptor.php create mode 100644 lam/lib/3rdParty/composer/web-auth/metadata-service/src/Version.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/LICENSE create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/README.md create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/composer.json create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AndroidKeyAttestationStatementSupport.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AndroidSafetyNetAttestationStatementSupport.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationObject.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationObjectLoader.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatement.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatementSupport.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatementSupportManager.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/FidoU2FAttestationStatementSupport.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/NoneAttestationStatementSupport.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/PackedAttestationStatementSupport.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/TPMAttestationStatementSupport.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestedCredentialData.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtension.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientInputs.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputs.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputsLoader.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputChecker.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputCheckerHandler.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputError.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAssertionResponse.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAssertionResponseValidator.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAttestationResponse.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAttestationResponseValidator.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorData.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorResponse.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorSelectionCriteria.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/CertificateToolbox.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/CollectedClientData.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/Credential.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredential.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialCreationOptions.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptor.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptorCollection.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialEntity.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialLoader.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialOptions.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialParameters.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialRequestOptions.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialRpEntity.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialSource.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialSourceRepository.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialUserEntity.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/Server.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/StringStream.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TokenBinding/IgnoreTokenBindingHandler.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TokenBinding/TokenBinding.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TokenBinding/TokenBindingHandler.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TokenBinding/TokenBindingNotSupportedHandler.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/CertificateTrustPath.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/EcdaaKeyIdTrustPath.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/EmptyTrustPath.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/TrustPath.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/TrustPathLoader.php create mode 100644 lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/Util/CoseSignatureFixer.php diff --git a/lam-packaging/debian/copyright b/lam-packaging/debian/copyright index cafeac9d..e39b6552 100644 --- a/lam-packaging/debian/copyright +++ b/lam-packaging/debian/copyright @@ -179,6 +179,7 @@ A: information, contact: tavmjong @ free . fr. B: + MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -200,6 +201,7 @@ B: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. C: + New BSD License Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -391,6 +393,7 @@ D: Library. E: + Duo Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -415,37 +418,73 @@ E: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -F: - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +F: + 3-Clause BSD License -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS -IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +G: + 2-Clause BSD License + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. Programs and licenses with other licenses and/or authors than the main license and authors: +lib/3rdParty/composer/beberlei G 2013 Benjamin Eberlei +lib/3rdParty/composer/composer B Nils Adermann, Jordi Boggiano +lib/3rdParty/composer/fgrosse B 2015 Friedrich Große +lib/3rdParty/composer/nyholm B 2016 Tobias Nyholm +lib/3rdParty/composer/paragonie B 2015 Paragon Initiative Enterprises +lib/3rdParty/composer/php-http B 2015 PHP HTTP Team +lib/3rdParty/composer/psr B 2018 PHP Framework Interoperability Group +lib/3rdParty/composer/ramsey B 2018 Ben Ramsey +lib/3rdParty/composer/spomky-labs B 2018 Spomky-Labs +lib/3rdParty/composer/symfony B 2019 Fabien Potencier +lib/3rdParty/composer/web-auth B 2018 Spomky-Labs lib/3rdParty/tcpdf D 2018 Nicola Asuni - Tecnick.com LTD lib/3rdParty/tcpdf/fonts/DejaVu*.ttf A Public Domain, Bitstream, Inc., Tavmjong Bah lib/3rdParty/tcpdf/fonts/DejaVu*.z A Public Domain, Bitstream, Inc., Tavmjong Bah diff --git a/lam/composer.lock b/lam/composer.lock new file mode 100644 index 00000000..c7cd584c --- /dev/null +++ b/lam/composer.lock @@ -0,0 +1,901 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "df09efa5a29ab9799e4e0c0ff066bfef", + "packages": [ + { + "name": "beberlei/assert", + "version": "v3.2.6", + "source": { + "type": "git", + "url": "https://github.com/beberlei/assert.git", + "reference": "99508be011753690fe108ded450f5caaae180cfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beberlei/assert/zipball/99508be011753690fe108ded450f5caaae180cfa", + "reference": "99508be011753690fe108ded450f5caaae180cfa", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "php": "^7" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "phpstan/phpstan-shim": "*", + "phpunit/phpunit": ">=6.0.0 <8" + }, + "suggest": { + "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" + }, + "type": "library", + "autoload": { + "psr-4": { + "Assert\\": "lib/Assert" + }, + "files": [ + "lib/Assert/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de", + "role": "Lead Developer" + }, + { + "name": "Richard Quadling", + "email": "rquadling@gmail.com", + "role": "Collaborator" + } + ], + "description": "Thin assertion library for input validation in business models.", + "keywords": [ + "assert", + "assertion", + "validation" + ], + "time": "2019-10-10T10:33:57+00:00" + }, + { + "name": "fgrosse/phpasn1", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/fgrosse/PHPASN1.git", + "reference": "7ebf2a09084a7bbdb7b879c66fdf7ad80461bbe8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/7ebf2a09084a7bbdb7b879c66fdf7ad80461bbe8", + "reference": "7ebf2a09084a7bbdb7b879c66fdf7ad80461bbe8", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.3", + "satooshi/php-coveralls": "~2.0" + }, + "suggest": { + "ext-gmp": "GMP is the preferred extension for big integer calculations", + "php-curl": "For loading OID information from the web if they have not bee defined statically" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "FG\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Friedrich Große", + "email": "friedrich.grosse@gmail.com", + "homepage": "https://github.com/FGrosse", + "role": "Author" + }, + { + "name": "All contributors", + "homepage": "https://github.com/FGrosse/PHPASN1/contributors" + } + ], + "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.", + "homepage": "https://github.com/FGrosse/PHPASN1", + "keywords": [ + "DER", + "asn.1", + "asn1", + "ber", + "binary", + "decoding", + "encoding", + "x.509", + "x.690", + "x509", + "x690" + ], + "time": "2018-12-02T01:34:34+00:00" + }, + { + "name": "nyholm/psr7", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "55ff6b76573f5b242554c9775792bd59fb52e11c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/55ff6b76573f5b242554c9775792bd59fb52e11c", + "reference": "55ff6b76573f5b242554c9775792bd59fb52e11c", + "shasum": "" + }, + "require": { + "php": "^7.1", + "php-http/message-factory": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "dev-master", + "php-http/psr7-integration-tests": "dev-master", + "phpunit/phpunit": "^7.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "http://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "time": "2019-09-05T13:24:16+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "time": "2018-07-02T15:55:56+00:00" + }, + { + "name": "php-http/message-factory", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-http/message-factory.git", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Factory interfaces for PSR-7 HTTP Message", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "stream", + "uri" + ], + "time": "2015-12-19T14:08:53+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "496a823ef742b632934724bf769560c2a5c7c44e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/496a823ef742b632934724bf769560c2a5c7c44e", + "reference": "496a823ef742b632934724bf769560c2a5c7c44e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "time": "2018-10-30T23:29:13+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "ramsey/uuid", + "version": "3.8.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/d09ea80159c1929d75b3f9c60504d613aeb4a1e3", + "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "^1.0|^2.0|9.99.99", + "php": "^5.4 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^1.0 | ~2.0.0", + "doctrine/annotations": "~1.2.0", + "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ~2.1.0", + "ircmaxell/random-lib": "^1.1", + "jakub-onderka/php-parallel-lint": "^0.9.0", + "mockery/mockery": "^0.9.9", + "moontoast/math": "^1.1", + "php-mock/php-mock-phpunit": "^0.3|^1.1", + "phpunit/phpunit": "^4.7|^5.0|^6.5", + "squizlabs/php_codesniffer": "^2.3" + }, + "suggest": { + "ext-ctype": "Provides support for PHP Ctype functions", + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + }, + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "time": "2018-07-19T23:38:55+00:00" + }, + { + "name": "spomky-labs/base64url", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/base64url.git", + "reference": "3eb46a1de803f0078962d910e3a2759224a68c61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/3eb46a1de803f0078962d910e3a2759224a68c61", + "reference": "3eb46a1de803f0078962d910e3a2759224a68c61", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Base64Url\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky-Labs/base64url/contributors" + } + ], + "description": "Base 64 URL Safe Encoding/Decoding PHP Library", + "homepage": "https://github.com/Spomky-Labs/base64url", + "keywords": [ + "base64", + "rfc4648", + "safe", + "url" + ], + "time": "2018-08-16T15:44:20+00:00" + }, + { + "name": "spomky-labs/cbor-php", + "version": "v1.0.7", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/cbor-php.git", + "reference": "f937d527b05bb3ec3f6b8670ece38ccaf9a0eddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/f937d527b05bb3ec3f6b8670ece38ccaf9a0eddd", + "reference": "f937d527b05bb3ec3f6b8670ece38ccaf9a0eddd", + "shasum": "" + }, + "require": { + "beberlei/assert": "^3.2", + "ext-gmp": "*", + "php": "^7.1|^8.0", + "spomky-labs/base64url": "^1.0|^2.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-beberlei-assert": "^0.11.0", + "phpstan/phpstan-deprecation-rules": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11", + "phpunit/phpunit": "^7.5|^8.0", + "rector/rector": "^0.5" + }, + "suggest": { + "ext-bcmath": "BCMath extension needed to handle the Big Float and Decimal Fraction Tags" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "CBOR\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/cbor-php/contributors" + } + ], + "description": "CBOR Encoder/Decoder for PHP", + "keywords": [ + "Concise Binary Object Representation", + "RFC7049", + "cbor" + ], + "time": "2019-08-15T14:53:55+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "web-auth/cose-lib", + "version": "v2.1.7", + "source": { + "type": "git", + "url": "https://github.com/web-auth/cose-lib.git", + "reference": "8d1c37bac6e5db8d502b7735448d416f05fb4c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/8d1c37bac6e5db8d502b7735448d416f05fb4c70", + "reference": "8d1c37bac6e5db8d502b7735448d416f05fb4c70", + "shasum": "" + }, + "require": { + "beberlei/assert": "^3.0", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "fgrosse/phpasn1": "^2.1", + "php": "^7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "v1.0": "1.0.x-dev", + "v1.1": "1.1.x-dev", + "v1.2": "1.2.x-dev", + "v2.0": "2.0.x-dev", + "v2.1": "2.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Cose\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/cose/contributors" + } + ], + "description": "CBOR Object Signing and Encryption (COSE) For PHP", + "homepage": "https://github.com/web-auth", + "keywords": [ + "COSE", + "RFC8152" + ], + "time": "2019-09-04T20:53:12+00:00" + }, + { + "name": "web-auth/metadata-service", + "version": "v2.1.7", + "source": { + "type": "git", + "url": "https://github.com/web-auth/webauthn-metadata-service.git", + "reference": "5fc754d00dfa05913260dc3781227dfa8ed7dbdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/5fc754d00dfa05913260dc3781227dfa8ed7dbdd", + "reference": "5fc754d00dfa05913260dc3781227dfa8ed7dbdd", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.1", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "v2.1": "2.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Webauthn\\MetadataService\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/metadata-service/contributors" + } + ], + "description": "Metadata Service for FIDO2/Webauthn", + "homepage": "https://github.com/web-auth", + "keywords": [ + "FIDO2", + "fido", + "webauthn" + ], + "time": "2019-09-04T20:53:12+00:00" + }, + { + "name": "web-auth/webauthn-lib", + "version": "v2.1.7", + "source": { + "type": "git", + "url": "https://github.com/web-auth/webauthn-lib.git", + "reference": "4cd346f2ef4d282296e503b7b1b3ef200347437b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/4cd346f2ef4d282296e503b7b1b3ef200347437b", + "reference": "4cd346f2ef4d282296e503b7b1b3ef200347437b", + "shasum": "" + }, + "require": { + "beberlei/assert": "^3.0", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "nyholm/psr7": "^1.1", + "php": "^7.2", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ramsey/uuid": "^3.8", + "spomky-labs/base64url": "^2.0", + "spomky-labs/cbor-php": "^1.0.2", + "web-auth/cose-lib": "self.version", + "web-auth/metadata-service": "self.version" + }, + "suggest": { + "web-token/jwt-signature-algorithm-ecdsa": "Recommended for the AndroidSafetyNet Attestation Statement support", + "web-token/jwt-signature-algorithm-eddsa": "Recommended for the AndroidSafetyNet Attestation Statement support", + "web-token/jwt-signature-algorithm-rsa": "Mandatory for the AndroidSafetyNet Attestation Statement support" + }, + "type": "library", + "extra": { + "branch-alias": { + "v1.0": "1.0.x-dev", + "v1.1": "1.1.x-dev", + "v1.2": "1.2.x-dev", + "v2.0": "2.0.x-dev", + "v2.1": "2.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Webauthn\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/webauthn-library/contributors" + } + ], + "description": "FIDO2/Webauthn Support For PHP", + "homepage": "https://github.com/web-auth", + "keywords": [ + "FIDO2", + "fido", + "webauthn" + ], + "time": "2019-09-09T12:04:09+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/lam/copyright b/lam/copyright index d2a46ec4..fe3f4fcb 100644 --- a/lam/copyright +++ b/lam/copyright @@ -178,6 +178,7 @@ A: information, contact: tavmjong @ free . fr. B: + MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -199,6 +200,7 @@ B: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. C: + New BSD License Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -390,6 +392,7 @@ D: Library. E: + Duo Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -415,36 +418,72 @@ E: THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. F: - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: + 3-Clause BSD License -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +G: + 2-Clause BSD License + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS -IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Programs and licenses with other licenses and/or authors than the main license and authors: +lib/3rdParty/composer/beberlei G 2013 Benjamin Eberlei +lib/3rdParty/composer/composer B Nils Adermann, Jordi Boggiano +lib/3rdParty/composer/fgrosse B 2015 Friedrich Große +lib/3rdParty/composer/nyholm B 2016 Tobias Nyholm +lib/3rdParty/composer/paragonie B 2015 Paragon Initiative Enterprises +lib/3rdParty/composer/php-http B 2015 PHP HTTP Team +lib/3rdParty/composer/psr B 2018 PHP Framework Interoperability Group +lib/3rdParty/composer/ramsey B 2018 Ben Ramsey +lib/3rdParty/composer/spomky-labs B 2018 Spomky-Labs +lib/3rdParty/composer/symfony B 2019 Fabien Potencier +lib/3rdParty/composer/web-auth B 2018 Spomky-Labs lib/3rdParty/tcpdf D 2018 Nicola Asuni - Tecnick.com LTD lib/3rdParty/tcpdf/fonts/DejaVu*.ttf A Public Domain, Bitstream, Inc., Tavmjong Bah lib/3rdParty/tcpdf/fonts/DejaVu*.z A Public Domain, Bitstream, Inc., Tavmjong Bah diff --git a/lam/lib/3rdParty/composer/autoload.php b/lam/lib/3rdParty/composer/autoload.php new file mode 100644 index 00000000..ef2a8557 --- /dev/null +++ b/lam/lib/3rdParty/composer/autoload.php @@ -0,0 +1,7 @@ +=6.0.0 <8" + }, + "scripts": { + "assert:generate-docs": "php bin/generate_method_docs.php", + "assert:cs-lint": "php-cs-fixer fix --diff -vvv --dry-run", + "assert:cs-fix": "php-cs-fixer fix . -vvv || true", + "assert:sa-code": "vendor/bin/phpstan analyse --configuration=phpstan-code.neon --no-progress --ansi -l 7 bin lib", + "assert:sa-tests": "vendor/bin/phpstan analyse --configuration=phpstan-tests.neon --no-progress --ansi -l 7 tests" + }, + "suggest": { + "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" + } +} diff --git a/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/Assert.php b/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/Assert.php new file mode 100644 index 00000000..6910258d --- /dev/null +++ b/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/Assert.php @@ -0,0 +1,96 @@ +notEmpty()->integer(); + * Assert::that($value)->nullOr()->string()->startsWith("Foo"); + * + * The assertion chain can be stateful, that means be careful when you reuse + * it. You should never pass around the chain. + */ + public static function that($value, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain + { + $assertionChain = new AssertionChain($value, $defaultMessage, $defaultPropertyPath); + + return $assertionChain->setAssertionClassName(static::$assertionClass); + } + + /** + * Start validation on a set of values, returns {@link AssertionChain}. + * + * @param mixed $values + * @param string|callable|null $defaultMessage + * @param string|null $defaultPropertyPath + * + * @return AssertionChain + */ + public static function thatAll($values, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain + { + return static::that($values, $defaultMessage, $defaultPropertyPath)->all(); + } + + /** + * Start validation and allow NULL, returns {@link AssertionChain}. + * + * @param mixed $value + * @param string|callable|null $defaultMessage + * @param string|null $defaultPropertyPath + * + * @return AssertionChain + */ + public static function thatNullOr($value, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain + { + return static::that($value, $defaultMessage, $defaultPropertyPath)->nullOr(); + } + + /** + * Create a lazy assertion object. + * + * @return LazyAssertion + */ + public static function lazy(): LazyAssertion + { + $lazyAssertion = new LazyAssertion(); + + return $lazyAssertion + ->setAssertClass(\get_called_class()) + ->setExceptionClass(static::$lazyAssertionExceptionClass); + } +} diff --git a/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/Assertion.php b/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/Assertion.php new file mode 100644 index 00000000..a8b04e52 --- /dev/null +++ b/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/Assertion.php @@ -0,0 +1,2825 @@ + + * + * @method static bool allAlnum(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric for all values. + * @method static bool allBase64(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined for all values. + * @method static bool allBetween(mixed[] $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit for all values. + * @method static bool allBetweenExclusive(mixed[] $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater than a lower limit, and less than an upper limit for all values. + * @method static bool allBetweenLength(mixed[] $value, int $minLength, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string length is between min and max lengths for all values. + * @method static bool allBoolean(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is php boolean for all values. + * @method static bool allChoice(mixed[] $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices for all values. + * @method static bool allChoicesNotEmpty(array[] $values, array $choices, string|callable $message = null, string $propertyPath = null) Determines if the values array has every choice as key and that this choice has content for all values. + * @method static bool allClassExists(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the class exists for all values. + * @method static bool allContains(mixed[] $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string contains a sequence of chars for all values. + * @method static bool allCount(array[]|Countable[]|ResourceBundle[]|SimpleXMLElement[] $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the count of countable is equal to count for all values. + * @method static bool allDate(string[] $value, string $format, string|callable $message = null, string $propertyPath = null) Assert that date is valid and corresponds to the given format for all values. + * @method static bool allDefined(mixed[] $constant, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined for all values. + * @method static bool allDigit(mixed[] $value, string|callable $message = null, string $propertyPath = null) Validates if an integer or integerish is a digit for all values. + * @method static bool allDirectory(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that a directory exists for all values. + * @method static bool allE164(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid E164 Phone Number for all values. + * @method static bool allEmail(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL) for all values. + * @method static bool allEndsWith(mixed[] $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string ends with a sequence of chars for all values. + * @method static bool allEq(mixed[] $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are equal (using ==) for all values. + * @method static bool allEqArraySubset(mixed[] $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that the array contains the subset for all values. + * @method static bool allExtensionLoaded(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded for all values. + * @method static bool allExtensionVersion(string[] $extension, string $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded and a specific version is installed for all values. + * @method static bool allFalse(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean False for all values. + * @method static bool allFile(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that a file exists for all values. + * @method static bool allFloat(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php float for all values. + * @method static bool allGreaterOrEqualThan(mixed[] $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater or equal than given limit for all values. + * @method static bool allGreaterThan(mixed[] $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater than given limit for all values. + * @method static bool allImplementsInterface(mixed[] $class, string $interfaceName, string|callable $message = null, string $propertyPath = null) Assert that the class implements the interface for all values. + * @method static bool allInArray(mixed[] $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. This is an alias of Assertion::choice() for all values. + * @method static bool allInteger(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer for all values. + * @method static bool allIntegerish(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer'ish for all values. + * @method static bool allInterfaceExists(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the interface exists for all values. + * @method static bool allIp(string[] $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 or IPv6 address for all values. + * @method static bool allIpv4(string[] $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address for all values. + * @method static bool allIpv6(string[] $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv6 address for all values. + * @method static bool allIsArray(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array for all values. + * @method static bool allIsArrayAccessible(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or an array-accessible object for all values. + * @method static bool allIsCallable(mixed[] $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is callable for all values. + * @method static bool allIsCountable(array[]|Countable[]|ResourceBundle[]|SimpleXMLElement[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is countable for all values. + * @method static bool allIsInstanceOf(mixed[] $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is instance of given class-name for all values. + * @method static bool allIsJsonString(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid json string for all values. + * @method static bool allIsObject(mixed[] $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is an object for all values. + * @method static bool allIsResource(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a resource for all values. + * @method static bool allIsTraversable(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or a traversable object for all values. + * @method static bool allKeyExists(mixed[] $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array for all values. + * @method static bool allKeyIsset(mixed[] $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object using isset() for all values. + * @method static bool allKeyNotExists(mixed[] $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key does not exist in an array for all values. + * @method static bool allLength(mixed[] $value, int $length, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string has a given length for all values. + * @method static bool allLessOrEqualThan(mixed[] $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less or equal than given limit for all values. + * @method static bool allLessThan(mixed[] $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less than given limit for all values. + * @method static bool allMax(mixed[] $value, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that a number is smaller as a given limit for all values. + * @method static bool allMaxCount(array[]|Countable[]|ResourceBundle[]|SimpleXMLElement[] $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at most $count elements for all values. + * @method static bool allMaxLength(mixed[] $value, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string value is not longer than $maxLength chars for all values. + * @method static bool allMethodExists(string[] $value, mixed $object, string|callable $message = null, string $propertyPath = null) Determines that the named method is defined in the provided object for all values. + * @method static bool allMin(mixed[] $value, mixed $minValue, string|callable $message = null, string $propertyPath = null) Assert that a value is at least as big as a given limit for all values. + * @method static bool allMinCount(array[]|Countable[]|ResourceBundle[]|SimpleXMLElement[] $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at least $count elements for all values. + * @method static bool allMinLength(mixed[] $value, int $minLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that a string is at least $minLength chars long for all values. + * @method static bool allNoContent(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is empty for all values. + * @method static bool allNotBlank(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is not blank for all values. + * @method static bool allNotContains(mixed[] $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string does not contains a sequence of chars for all values. + * @method static bool allNotEmpty(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is not empty for all values. + * @method static bool allNotEmptyKey(mixed[] $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object and its value is not empty for all values. + * @method static bool allNotEq(mixed[] $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not equal (using ==) for all values. + * @method static bool allNotInArray(mixed[] $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is not in array of choices for all values. + * @method static bool allNotIsInstanceOf(mixed[] $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is not instance of given class-name for all values. + * @method static bool allNotNull(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is not null for all values. + * @method static bool allNotRegex(mixed[] $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value does not match a regex for all values. + * @method static bool allNotSame(mixed[] $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not the same (using ===) for all values. + * @method static bool allNull(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is null for all values. + * @method static bool allNumeric(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is numeric for all values. + * @method static bool allObjectOrClass(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the value is an object, or a class that exists for all values. + * @method static bool allPhpVersion(string[] $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert on PHP version for all values. + * @method static bool allPropertiesExist(mixed[] $value, array $properties, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the properties all exist for all values. + * @method static bool allPropertyExists(mixed[] $value, string $property, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the property exists for all values. + * @method static bool allRange(mixed[] $value, mixed $minValue, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that value is in range of numbers for all values. + * @method static bool allReadable(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something readable for all values. + * @method static bool allRegex(mixed[] $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value matches a regex for all values. + * @method static bool allSame(mixed[] $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are the same (using ===) for all values. + * @method static bool allSatisfy(mixed[] $value, callable $callback, string|callable $message = null, string $propertyPath = null) Assert that the provided value is valid according to a callback for all values. + * @method static bool allScalar(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a PHP scalar for all values. + * @method static bool allStartsWith(mixed[] $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string starts with a sequence of chars for all values. + * @method static bool allString(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a string for all values. + * @method static bool allSubclassOf(mixed[] $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is subclass of given class-name for all values. + * @method static bool allTrue(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean True for all values. + * @method static bool allUrl(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is an URL for all values. + * @method static bool allUuid(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid UUID for all values. + * @method static bool allVersion(string[] $version1, string $operator, string $version2, string|callable $message = null, string $propertyPath = null) Assert comparison of two versions for all values. + * @method static bool allWriteable(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something writeable for all values. + * @method static bool nullOrAlnum(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric or that the value is null. + * @method static bool nullOrBase64(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined or that the value is null. + * @method static bool nullOrBetween(mixed|null $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit or that the value is null. + * @method static bool nullOrBetweenExclusive(mixed|null $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater than a lower limit, and less than an upper limit or that the value is null. + * @method static bool nullOrBetweenLength(mixed|null $value, int $minLength, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string length is between min and max lengths or that the value is null. + * @method static bool nullOrBoolean(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is php boolean or that the value is null. + * @method static bool nullOrChoice(mixed|null $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices or that the value is null. + * @method static bool nullOrChoicesNotEmpty(array|null $values, array $choices, string|callable $message = null, string $propertyPath = null) Determines if the values array has every choice as key and that this choice has content or that the value is null. + * @method static bool nullOrClassExists(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the class exists or that the value is null. + * @method static bool nullOrContains(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string contains a sequence of chars or that the value is null. + * @method static bool nullOrCount(array|Countable|ResourceBundle|SimpleXMLElement|null $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the count of countable is equal to count or that the value is null. + * @method static bool nullOrDate(string|null $value, string $format, string|callable $message = null, string $propertyPath = null) Assert that date is valid and corresponds to the given format or that the value is null. + * @method static bool nullOrDefined(mixed|null $constant, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined or that the value is null. + * @method static bool nullOrDigit(mixed|null $value, string|callable $message = null, string $propertyPath = null) Validates if an integer or integerish is a digit or that the value is null. + * @method static bool nullOrDirectory(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that a directory exists or that the value is null. + * @method static bool nullOrE164(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid E164 Phone Number or that the value is null. + * @method static bool nullOrEmail(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL) or that the value is null. + * @method static bool nullOrEndsWith(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string ends with a sequence of chars or that the value is null. + * @method static bool nullOrEq(mixed|null $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are equal (using ==) or that the value is null. + * @method static bool nullOrEqArraySubset(mixed|null $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that the array contains the subset or that the value is null. + * @method static bool nullOrExtensionLoaded(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded or that the value is null. + * @method static bool nullOrExtensionVersion(string|null $extension, string $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded and a specific version is installed or that the value is null. + * @method static bool nullOrFalse(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean False or that the value is null. + * @method static bool nullOrFile(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that a file exists or that the value is null. + * @method static bool nullOrFloat(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php float or that the value is null. + * @method static bool nullOrGreaterOrEqualThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater or equal than given limit or that the value is null. + * @method static bool nullOrGreaterThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater than given limit or that the value is null. + * @method static bool nullOrImplementsInterface(mixed|null $class, string $interfaceName, string|callable $message = null, string $propertyPath = null) Assert that the class implements the interface or that the value is null. + * @method static bool nullOrInArray(mixed|null $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. This is an alias of Assertion::choice() or that the value is null. + * @method static bool nullOrInteger(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer or that the value is null. + * @method static bool nullOrIntegerish(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer'ish or that the value is null. + * @method static bool nullOrInterfaceExists(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the interface exists or that the value is null. + * @method static bool nullOrIp(string|null $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 or IPv6 address or that the value is null. + * @method static bool nullOrIpv4(string|null $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address or that the value is null. + * @method static bool nullOrIpv6(string|null $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv6 address or that the value is null. + * @method static bool nullOrIsArray(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or that the value is null. + * @method static bool nullOrIsArrayAccessible(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or an array-accessible object or that the value is null. + * @method static bool nullOrIsCallable(mixed|null $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is callable or that the value is null. + * @method static bool nullOrIsCountable(array|Countable|ResourceBundle|SimpleXMLElement|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is countable or that the value is null. + * @method static bool nullOrIsInstanceOf(mixed|null $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is instance of given class-name or that the value is null. + * @method static bool nullOrIsJsonString(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid json string or that the value is null. + * @method static bool nullOrIsObject(mixed|null $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is an object or that the value is null. + * @method static bool nullOrIsResource(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a resource or that the value is null. + * @method static bool nullOrIsTraversable(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or a traversable object or that the value is null. + * @method static bool nullOrKeyExists(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array or that the value is null. + * @method static bool nullOrKeyIsset(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object using isset() or that the value is null. + * @method static bool nullOrKeyNotExists(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key does not exist in an array or that the value is null. + * @method static bool nullOrLength(mixed|null $value, int $length, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string has a given length or that the value is null. + * @method static bool nullOrLessOrEqualThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less or equal than given limit or that the value is null. + * @method static bool nullOrLessThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less than given limit or that the value is null. + * @method static bool nullOrMax(mixed|null $value, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that a number is smaller as a given limit or that the value is null. + * @method static bool nullOrMaxCount(array|Countable|ResourceBundle|SimpleXMLElement|null $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at most $count elements or that the value is null. + * @method static bool nullOrMaxLength(mixed|null $value, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string value is not longer than $maxLength chars or that the value is null. + * @method static bool nullOrMethodExists(string|null $value, mixed $object, string|callable $message = null, string $propertyPath = null) Determines that the named method is defined in the provided object or that the value is null. + * @method static bool nullOrMin(mixed|null $value, mixed $minValue, string|callable $message = null, string $propertyPath = null) Assert that a value is at least as big as a given limit or that the value is null. + * @method static bool nullOrMinCount(array|Countable|ResourceBundle|SimpleXMLElement|null $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at least $count elements or that the value is null. + * @method static bool nullOrMinLength(mixed|null $value, int $minLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that a string is at least $minLength chars long or that the value is null. + * @method static bool nullOrNoContent(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is empty or that the value is null. + * @method static bool nullOrNotBlank(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is not blank or that the value is null. + * @method static bool nullOrNotContains(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string does not contains a sequence of chars or that the value is null. + * @method static bool nullOrNotEmpty(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is not empty or that the value is null. + * @method static bool nullOrNotEmptyKey(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object and its value is not empty or that the value is null. + * @method static bool nullOrNotEq(mixed|null $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not equal (using ==) or that the value is null. + * @method static bool nullOrNotInArray(mixed|null $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is not in array of choices or that the value is null. + * @method static bool nullOrNotIsInstanceOf(mixed|null $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is not instance of given class-name or that the value is null. + * @method static bool nullOrNotNull(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is not null or that the value is null. + * @method static bool nullOrNotRegex(mixed|null $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value does not match a regex or that the value is null. + * @method static bool nullOrNotSame(mixed|null $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not the same (using ===) or that the value is null. + * @method static bool nullOrNull(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is null or that the value is null. + * @method static bool nullOrNumeric(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is numeric or that the value is null. + * @method static bool nullOrObjectOrClass(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is an object, or a class that exists or that the value is null. + * @method static bool nullOrPhpVersion(string|null $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert on PHP version or that the value is null. + * @method static bool nullOrPropertiesExist(mixed|null $value, array $properties, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the properties all exist or that the value is null. + * @method static bool nullOrPropertyExists(mixed|null $value, string $property, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the property exists or that the value is null. + * @method static bool nullOrRange(mixed|null $value, mixed $minValue, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that value is in range of numbers or that the value is null. + * @method static bool nullOrReadable(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something readable or that the value is null. + * @method static bool nullOrRegex(mixed|null $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value matches a regex or that the value is null. + * @method static bool nullOrSame(mixed|null $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are the same (using ===) or that the value is null. + * @method static bool nullOrSatisfy(mixed|null $value, callable $callback, string|callable $message = null, string $propertyPath = null) Assert that the provided value is valid according to a callback or that the value is null. + * @method static bool nullOrScalar(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a PHP scalar or that the value is null. + * @method static bool nullOrStartsWith(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string starts with a sequence of chars or that the value is null. + * @method static bool nullOrString(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a string or that the value is null. + * @method static bool nullOrSubclassOf(mixed|null $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is subclass of given class-name or that the value is null. + * @method static bool nullOrTrue(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean True or that the value is null. + * @method static bool nullOrUrl(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an URL or that the value is null. + * @method static bool nullOrUuid(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid UUID or that the value is null. + * @method static bool nullOrVersion(string|null $version1, string $operator, string $version2, string|callable $message = null, string $propertyPath = null) Assert comparison of two versions or that the value is null. + * @method static bool nullOrWriteable(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something writeable or that the value is null. + */ +class Assertion +{ + const INVALID_FLOAT = 9; + const INVALID_INTEGER = 10; + const INVALID_DIGIT = 11; + const INVALID_INTEGERISH = 12; + const INVALID_BOOLEAN = 13; + const VALUE_EMPTY = 14; + const VALUE_NULL = 15; + const VALUE_NOT_NULL = 25; + const INVALID_STRING = 16; + const INVALID_REGEX = 17; + const INVALID_MIN_LENGTH = 18; + const INVALID_MAX_LENGTH = 19; + const INVALID_STRING_START = 20; + const INVALID_STRING_CONTAINS = 21; + const INVALID_CHOICE = 22; + const INVALID_NUMERIC = 23; + const INVALID_ARRAY = 24; + const INVALID_KEY_EXISTS = 26; + const INVALID_NOT_BLANK = 27; + const INVALID_INSTANCE_OF = 28; + const INVALID_SUBCLASS_OF = 29; + const INVALID_RANGE = 30; + const INVALID_ALNUM = 31; + const INVALID_TRUE = 32; + const INVALID_EQ = 33; + const INVALID_SAME = 34; + const INVALID_MIN = 35; + const INVALID_MAX = 36; + const INVALID_LENGTH = 37; + const INVALID_FALSE = 38; + const INVALID_STRING_END = 39; + const INVALID_UUID = 40; + const INVALID_COUNT = 41; + const INVALID_NOT_EQ = 42; + const INVALID_NOT_SAME = 43; + const INVALID_TRAVERSABLE = 44; + const INVALID_ARRAY_ACCESSIBLE = 45; + const INVALID_KEY_ISSET = 46; + const INVALID_VALUE_IN_ARRAY = 47; + const INVALID_E164 = 48; + const INVALID_BASE64 = 49; + const INVALID_NOT_REGEX = 50; + const INVALID_DIRECTORY = 101; + const INVALID_FILE = 102; + const INVALID_READABLE = 103; + const INVALID_WRITEABLE = 104; + const INVALID_CLASS = 105; + const INVALID_INTERFACE = 106; + const INVALID_FILE_NOT_EXISTS = 107; + const INVALID_EMAIL = 201; + const INTERFACE_NOT_IMPLEMENTED = 202; + const INVALID_URL = 203; + const INVALID_NOT_INSTANCE_OF = 204; + const VALUE_NOT_EMPTY = 205; + const INVALID_JSON_STRING = 206; + const INVALID_OBJECT = 207; + const INVALID_METHOD = 208; + const INVALID_SCALAR = 209; + const INVALID_LESS = 210; + const INVALID_LESS_OR_EQUAL = 211; + const INVALID_GREATER = 212; + const INVALID_GREATER_OR_EQUAL = 213; + const INVALID_DATE = 214; + const INVALID_CALLABLE = 215; + const INVALID_KEY_NOT_EXISTS = 216; + const INVALID_SATISFY = 217; + const INVALID_IP = 218; + const INVALID_BETWEEN = 219; + const INVALID_BETWEEN_EXCLUSIVE = 220; + const INVALID_EXTENSION = 222; + const INVALID_CONSTANT = 221; + const INVALID_VERSION = 223; + const INVALID_PROPERTY = 224; + const INVALID_RESOURCE = 225; + const INVALID_COUNTABLE = 226; + const INVALID_MIN_COUNT = 227; + const INVALID_MAX_COUNT = 228; + const INVALID_STRING_NOT_CONTAINS = 229; + + /** + * Exception to throw when an assertion failed. + * + * @var string + */ + protected static $exceptionClass = InvalidArgumentException::class; + + /** + * Assert that two values are equal (using ==). + * + * @param mixed $value + * @param mixed $value2 + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function eq($value, $value2, $message = null, string $propertyPath = null): bool + { + if ($value != $value2) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" does not equal expected value "%s".'), + static::stringify($value), + static::stringify($value2) + ); + + throw static::createException($value, $message, static::INVALID_EQ, $propertyPath, ['expected' => $value2]); + } + + return true; + } + + /** + * Assert that the array contains the subset. + * + * @param mixed $value + * @param mixed $value2 + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function eqArraySubset($value, $value2, $message = null, string $propertyPath = null): bool + { + static::isArray($value, $message, $propertyPath); + static::isArray($value2, $message, $propertyPath); + + $patched = \array_replace_recursive($value, $value2); + static::eq($patched, $value, $message, $propertyPath); + + return true; + } + + /** + * Assert that two values are the same (using ===). + * + * @param mixed $value + * @param mixed $value2 + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function same($value, $value2, $message = null, string $propertyPath = null): bool + { + if ($value !== $value2) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not the same as expected value "%s".'), + static::stringify($value), + static::stringify($value2) + ); + + throw static::createException($value, $message, static::INVALID_SAME, $propertyPath, ['expected' => $value2]); + } + + return true; + } + + /** + * Assert that two values are not equal (using ==). + * + * @param mixed $value1 + * @param mixed $value2 + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function notEq($value1, $value2, $message = null, string $propertyPath = null): bool + { + if ($value1 == $value2) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" was not expected to be equal to value "%s".'), + static::stringify($value1), + static::stringify($value2) + ); + throw static::createException($value1, $message, static::INVALID_NOT_EQ, $propertyPath, ['expected' => $value2]); + } + + return true; + } + + /** + * Assert that two values are not the same (using ===). + * + * @param mixed $value1 + * @param mixed $value2 + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function notSame($value1, $value2, $message = null, string $propertyPath = null): bool + { + if ($value1 === $value2) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" was not expected to be the same as value "%s".'), + static::stringify($value1), + static::stringify($value2) + ); + throw static::createException($value1, $message, static::INVALID_NOT_SAME, $propertyPath, ['expected' => $value2]); + } + + return true; + } + + /** + * Assert that value is not in array of choices. + * + * @param mixed $value + * @param array $choices + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function notInArray($value, array $choices, $message = null, string $propertyPath = null): bool + { + if (true === \in_array($value, $choices)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" was not expected to be an element of the values: %s'), + static::stringify($value), + static::stringify($choices) + ); + throw static::createException($value, $message, static::INVALID_VALUE_IN_ARRAY, $propertyPath, ['choices' => $choices]); + } + + return true; + } + + /** + * Assert that value is a php integer. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function integer($value, $message = null, string $propertyPath = null): bool + { + if (!\is_int($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not an integer.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_INTEGER, $propertyPath); + } + + return true; + } + + /** + * Assert that value is a php float. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function float($value, $message = null, string $propertyPath = null): bool + { + if (!\is_float($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not a float.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_FLOAT, $propertyPath); + } + + return true; + } + + /** + * Validates if an integer or integerish is a digit. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function digit($value, $message = null, string $propertyPath = null): bool + { + if (!\ctype_digit((string)$value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not a digit.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_DIGIT, $propertyPath); + } + + return true; + } + + /** + * Assert that value is a php integer'ish. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function integerish($value, $message = null, string $propertyPath = null): bool + { + if ( + \is_resource($value) || + \is_object($value) || + \is_bool($value) || + \is_null($value) || + \is_array($value) || + (\is_string($value) && '' == $value) || + ( + \strval(\intval($value)) !== \strval($value) && + \strval(\intval($value)) !== \strval(\ltrim($value, '0')) && + '' !== \strval(\intval($value)) && + '' !== \strval(\ltrim($value, '0')) + ) + ) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not an integer or a number castable to integer.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_INTEGERISH, $propertyPath); + } + + return true; + } + + /** + * Assert that value is php boolean. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function boolean($value, $message = null, string $propertyPath = null): bool + { + if (!\is_bool($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not a boolean.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_BOOLEAN, $propertyPath); + } + + return true; + } + + /** + * Assert that value is a PHP scalar. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function scalar($value, $message = null, string $propertyPath = null): bool + { + if (!\is_scalar($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not a scalar.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_SCALAR, $propertyPath); + } + + return true; + } + + /** + * Assert that value is not empty. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function notEmpty($value, $message = null, string $propertyPath = null): bool + { + if (empty($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is empty, but non empty value was expected.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::VALUE_EMPTY, $propertyPath); + } + + return true; + } + + /** + * Assert that value is empty. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function noContent($value, $message = null, string $propertyPath = null): bool + { + if (!empty($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not empty, but empty value was expected.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::VALUE_NOT_EMPTY, $propertyPath); + } + + return true; + } + + /** + * Assert that value is null. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + */ + public static function null($value, $message = null, string $propertyPath = null): bool + { + if (null !== $value) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not null, but null value was expected.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::VALUE_NOT_NULL, $propertyPath); + } + + return true; + } + + /** + * Assert that value is not null. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function notNull($value, $message = null, string $propertyPath = null): bool + { + if (null === $value) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is null, but non null value was expected.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::VALUE_NULL, $propertyPath); + } + + return true; + } + + /** + * Assert that value is a string. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function string($value, $message = null, string $propertyPath = null) + { + if (!\is_string($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" expected to be string, type %s given.'), + static::stringify($value), + \gettype($value) + ); + + throw static::createException($value, $message, static::INVALID_STRING, $propertyPath); + } + + return true; + } + + /** + * Assert that value matches a regex. + * + * @param mixed $value + * @param string $pattern + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function regex($value, $pattern, $message = null, string $propertyPath = null): bool + { + static::string($value, $message, $propertyPath); + + if (!\preg_match($pattern, $value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" does not match expression.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_REGEX, $propertyPath, ['pattern' => $pattern]); + } + + return true; + } + + /** + * Assert that value does not match a regex. + * + * @param mixed $value + * @param string $pattern + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function notRegex($value, $pattern, $message = null, string $propertyPath = null): bool + { + static::string($value, $message, $propertyPath); + + if (\preg_match($pattern, $value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" matches expression.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_NOT_REGEX, $propertyPath, ['pattern' => $pattern]); + } + + return true; + } + + /** + * Assert that string has a given length. + * + * @param mixed $value + * @param int $length + * @param string|callable|null $message + * @param string|null $propertyPath + * @param string $encoding + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function length($value, $length, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + { + static::string($value, $message, $propertyPath); + + if (\mb_strlen($value, $encoding) !== $length) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" has to be %d exactly characters long, but length is %d.'), + static::stringify($value), + $length, + \mb_strlen($value, $encoding) + ); + + throw static::createException($value, $message, static::INVALID_LENGTH, $propertyPath, ['length' => $length, 'encoding' => $encoding]); + } + + return true; + } + + /** + * Assert that a string is at least $minLength chars long. + * + * @param mixed $value + * @param int $minLength + * @param string|callable|null $message + * @param string|null $propertyPath + * @param string $encoding + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function minLength($value, $minLength, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + { + static::string($value, $message, $propertyPath); + + if (\mb_strlen($value, $encoding) < $minLength) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is too short, it should have at least %d characters, but only has %d characters.'), + static::stringify($value), + $minLength, + \mb_strlen($value, $encoding) + ); + + throw static::createException($value, $message, static::INVALID_MIN_LENGTH, $propertyPath, ['min_length' => $minLength, 'encoding' => $encoding]); + } + + return true; + } + + /** + * Assert that string value is not longer than $maxLength chars. + * + * @param mixed $value + * @param int $maxLength + * @param string|callable|null $message + * @param string|null $propertyPath + * @param string $encoding + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function maxLength($value, $maxLength, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + { + static::string($value, $message, $propertyPath); + + if (\mb_strlen($value, $encoding) > $maxLength) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is too long, it should have no more than %d characters, but has %d characters.'), + static::stringify($value), + $maxLength, + \mb_strlen($value, $encoding) + ); + + throw static::createException($value, $message, static::INVALID_MAX_LENGTH, $propertyPath, ['max_length' => $maxLength, 'encoding' => $encoding]); + } + + return true; + } + + /** + * Assert that string length is between min and max lengths. + * + * @param mixed $value + * @param int $minLength + * @param int $maxLength + * @param string|callable|null $message + * @param string|null $propertyPath + * @param string $encoding + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function betweenLength($value, $minLength, $maxLength, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + { + static::string($value, $message, $propertyPath); + static::minLength($value, $minLength, $message, $propertyPath, $encoding); + static::maxLength($value, $maxLength, $message, $propertyPath, $encoding); + + return true; + } + + /** + * Assert that string starts with a sequence of chars. + * + * @param mixed $string + * @param string $needle + * @param string|callable|null $message + * @param string|null $propertyPath + * @param string $encoding + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function startsWith($string, $needle, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + { + static::string($string, $message, $propertyPath); + + if (0 !== \mb_strpos($string, $needle, null, $encoding)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" does not start with "%s".'), + static::stringify($string), + static::stringify($needle) + ); + + throw static::createException($string, $message, static::INVALID_STRING_START, $propertyPath, ['needle' => $needle, 'encoding' => $encoding]); + } + + return true; + } + + /** + * Assert that string ends with a sequence of chars. + * + * @param mixed $string + * @param string $needle + * @param string|callable|null $message + * @param string|null $propertyPath + * @param string $encoding + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function endsWith($string, $needle, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + { + static::string($string, $message, $propertyPath); + + $stringPosition = \mb_strlen($string, $encoding) - \mb_strlen($needle, $encoding); + + if (\mb_strripos($string, $needle, null, $encoding) !== $stringPosition) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" does not end with "%s".'), + static::stringify($string), + static::stringify($needle) + ); + + throw static::createException($string, $message, static::INVALID_STRING_END, $propertyPath, ['needle' => $needle, 'encoding' => $encoding]); + } + + return true; + } + + /** + * Assert that string contains a sequence of chars. + * + * @param mixed $string + * @param string $needle + * @param string|callable|null $message + * @param string|null $propertyPath + * @param string $encoding + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function contains($string, $needle, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + { + static::string($string, $message, $propertyPath); + + if (false === \mb_strpos($string, $needle, null, $encoding)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" does not contain "%s".'), + static::stringify($string), + static::stringify($needle) + ); + + throw static::createException($string, $message, static::INVALID_STRING_CONTAINS, $propertyPath, ['needle' => $needle, 'encoding' => $encoding]); + } + + return true; + } + + /** + * Assert that string does not contains a sequence of chars. + * + * @param mixed $string + * @param string $needle + * @param string|callable|null $message + * @param string|null $propertyPath + * @param string $encoding + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function notContains($string, $needle, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + { + static::string($string, $message, $propertyPath); + + if (false !== \mb_strpos($string, $needle, null, $encoding)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" contains "%s".'), + static::stringify($string), + static::stringify($needle) + ); + + throw static::createException($string, $message, static::INVALID_STRING_NOT_CONTAINS, $propertyPath, ['needle' => $needle, 'encoding' => $encoding]); + } + + return true; + } + + /** + * Assert that value is in array of choices. + * + * @param mixed $value + * @param array $choices + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function choice($value, array $choices, $message = null, string $propertyPath = null): bool + { + if (!\in_array($value, $choices, true)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not an element of the valid values: %s'), + static::stringify($value), + \implode(', ', \array_map([\get_called_class(), 'stringify'], $choices)) + ); + + throw static::createException($value, $message, static::INVALID_CHOICE, $propertyPath, ['choices' => $choices]); + } + + return true; + } + + /** + * Assert that value is in array of choices. + * + * This is an alias of {@see choice()}. + * + * @param mixed $value + * @param array $choices + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function inArray($value, array $choices, $message = null, string $propertyPath = null): bool + { + return static::choice($value, $choices, $message, $propertyPath); + } + + /** + * Assert that value is numeric. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function numeric($value, $message = null, string $propertyPath = null): bool + { + if (!\is_numeric($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not numeric.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_NUMERIC, $propertyPath); + } + + return true; + } + + /** + * Assert that value is a resource. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + */ + public static function isResource($value, $message = null, string $propertyPath = null): bool + { + if (!\is_resource($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not a resource.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_RESOURCE, $propertyPath); + } + + return true; + } + + /** + * Assert that value is an array. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function isArray($value, $message = null, string $propertyPath = null): bool + { + if (!\is_array($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not an array.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_ARRAY, $propertyPath); + } + + return true; + } + + /** + * Assert that value is an array or a traversable object. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function isTraversable($value, $message = null, string $propertyPath = null): bool + { + if (!\is_array($value) && !$value instanceof Traversable) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not an array and does not implement Traversable.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_TRAVERSABLE, $propertyPath); + } + + return true; + } + + /** + * Assert that value is an array or an array-accessible object. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function isArrayAccessible($value, $message = null, string $propertyPath = null): bool + { + if (!\is_array($value) && !$value instanceof ArrayAccess) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not an array and does not implement ArrayAccess.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_ARRAY_ACCESSIBLE, $propertyPath); + } + + return true; + } + + /** + * Assert that value is countable. + * + * @param array|Countable|ResourceBundle|SimpleXMLElement $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function isCountable($value, $message = null, string $propertyPath = null): bool + { + if (\function_exists('is_countable')) { + $assert = \is_countable($value); + } else { + $assert = \is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXMLElement; + } + + if (!$assert) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not an array and does not implement Countable.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_COUNTABLE, $propertyPath); + } + + return true; + } + + /** + * Assert that key exists in an array. + * + * @param mixed $value + * @param string|int $key + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function keyExists($value, $key, $message = null, string $propertyPath = null): bool + { + static::isArray($value, $message, $propertyPath); + + if (!\array_key_exists($key, $value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Array does not contain an element with key "%s"'), + static::stringify($key) + ); + + throw static::createException($value, $message, static::INVALID_KEY_EXISTS, $propertyPath, ['key' => $key]); + } + + return true; + } + + /** + * Assert that key does not exist in an array. + * + * @param mixed $value + * @param string|int $key + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function keyNotExists($value, $key, $message = null, string $propertyPath = null): bool + { + static::isArray($value, $message, $propertyPath); + + if (\array_key_exists($key, $value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Array contains an element with key "%s"'), + static::stringify($key) + ); + + throw static::createException($value, $message, static::INVALID_KEY_NOT_EXISTS, $propertyPath, ['key' => $key]); + } + + return true; + } + + /** + * Assert that key exists in an array/array-accessible object using isset(). + * + * @param mixed $value + * @param string|int $key + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function keyIsset($value, $key, $message = null, string $propertyPath = null): bool + { + static::isArrayAccessible($value, $message, $propertyPath); + + if (!isset($value[$key])) { + $message = \sprintf( + static::generateMessage($message ?: 'The element with key "%s" was not found'), + static::stringify($key) + ); + + throw static::createException($value, $message, static::INVALID_KEY_ISSET, $propertyPath, ['key' => $key]); + } + + return true; + } + + /** + * Assert that key exists in an array/array-accessible object and its value is not empty. + * + * @param mixed $value + * @param string|int $key + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function notEmptyKey($value, $key, $message = null, string $propertyPath = null): bool + { + static::keyIsset($value, $key, $message, $propertyPath); + static::notEmpty($value[$key], $message, $propertyPath); + + return true; + } + + /** + * Assert that value is not blank. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function notBlank($value, $message = null, string $propertyPath = null): bool + { + if (false === $value || (empty($value) && '0' != $value) || (\is_string($value) && '' === \trim($value))) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is blank, but was expected to contain a value.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_NOT_BLANK, $propertyPath); + } + + return true; + } + + /** + * Assert that value is instance of given class-name. + * + * @param mixed $value + * @param string $className + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function isInstanceOf($value, $className, $message = null, string $propertyPath = null): bool + { + if (!($value instanceof $className)) { + $message = \sprintf( + static::generateMessage($message ?: 'Class "%s" was expected to be instanceof of "%s" but is not.'), + static::stringify($value), + $className + ); + + throw static::createException($value, $message, static::INVALID_INSTANCE_OF, $propertyPath, ['class' => $className]); + } + + return true; + } + + /** + * Assert that value is not instance of given class-name. + * + * @param mixed $value + * @param string $className + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function notIsInstanceOf($value, $className, $message = null, string $propertyPath = null): bool + { + if ($value instanceof $className) { + $message = \sprintf( + static::generateMessage($message ?: 'Class "%s" was not expected to be instanceof of "%s".'), + static::stringify($value), + $className + ); + + throw static::createException($value, $message, static::INVALID_NOT_INSTANCE_OF, $propertyPath, ['class' => $className]); + } + + return true; + } + + /** + * Assert that value is subclass of given class-name. + * + * @param mixed $value + * @param string $className + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function subclassOf($value, $className, $message = null, string $propertyPath = null): bool + { + if (!\is_subclass_of($value, $className)) { + $message = \sprintf( + static::generateMessage($message ?: 'Class "%s" was expected to be subclass of "%s".'), + static::stringify($value), + $className + ); + + throw static::createException($value, $message, static::INVALID_SUBCLASS_OF, $propertyPath, ['class' => $className]); + } + + return true; + } + + /** + * Assert that value is in range of numbers. + * + * @param mixed $value + * @param mixed $minValue + * @param mixed $maxValue + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function range($value, $minValue, $maxValue, $message = null, string $propertyPath = null): bool + { + static::numeric($value, $message, $propertyPath); + + if ($value < $minValue || $value > $maxValue) { + $message = \sprintf( + static::generateMessage($message ?: 'Number "%s" was expected to be at least "%d" and at most "%d".'), + static::stringify($value), + static::stringify($minValue), + static::stringify($maxValue) + ); + + throw static::createException($value, $message, static::INVALID_RANGE, $propertyPath, ['min' => $minValue, 'max' => $maxValue]); + } + + return true; + } + + /** + * Assert that a value is at least as big as a given limit. + * + * @param mixed $value + * @param mixed $minValue + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function min($value, $minValue, $message = null, string $propertyPath = null): bool + { + static::numeric($value, $message, $propertyPath); + + if ($value < $minValue) { + $message = \sprintf( + static::generateMessage($message ?: 'Number "%s" was expected to be at least "%s".'), + static::stringify($value), + static::stringify($minValue) + ); + + throw static::createException($value, $message, static::INVALID_MIN, $propertyPath, ['min' => $minValue]); + } + + return true; + } + + /** + * Assert that a number is smaller as a given limit. + * + * @param mixed $value + * @param mixed $maxValue + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function max($value, $maxValue, $message = null, string $propertyPath = null): bool + { + static::numeric($value, $message, $propertyPath); + + if ($value > $maxValue) { + $message = \sprintf( + static::generateMessage($message ?: 'Number "%s" was expected to be at most "%s".'), + static::stringify($value), + static::stringify($maxValue) + ); + + throw static::createException($value, $message, static::INVALID_MAX, $propertyPath, ['max' => $maxValue]); + } + + return true; + } + + /** + * Assert that a file exists. + * + * @param string $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function file($value, $message = null, string $propertyPath = null): bool + { + static::string($value, $message, $propertyPath); + static::notEmpty($value, $message, $propertyPath); + + if (!\is_file($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'File "%s" was expected to exist.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_FILE, $propertyPath); + } + + return true; + } + + /** + * Assert that a directory exists. + * + * @param string $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function directory($value, $message = null, string $propertyPath = null): bool + { + static::string($value, $message, $propertyPath); + + if (!\is_dir($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Path "%s" was expected to be a directory.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_DIRECTORY, $propertyPath); + } + + return true; + } + + /** + * Assert that the value is something readable. + * + * @param string $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function readable($value, $message = null, string $propertyPath = null): bool + { + static::string($value, $message, $propertyPath); + + if (!\is_readable($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Path "%s" was expected to be readable.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_READABLE, $propertyPath); + } + + return true; + } + + /** + * Assert that the value is something writeable. + * + * @param string $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function writeable($value, $message = null, string $propertyPath = null): bool + { + static::string($value, $message, $propertyPath); + + if (!\is_writable($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Path "%s" was expected to be writeable.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_WRITEABLE, $propertyPath); + } + + return true; + } + + /** + * Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL). + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function email($value, $message = null, string $propertyPath = null): bool + { + static::string($value, $message, $propertyPath); + + if (!\filter_var($value, FILTER_VALIDATE_EMAIL)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" was expected to be a valid e-mail address.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_EMAIL, $propertyPath); + } + + return true; + } + + /** + * Assert that value is an URL. + * + * This code snipped was taken from the Symfony project and modified to the special demands of this method. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + * + * @see https://github.com/symfony/Validator/blob/master/Constraints/UrlValidator.php + * @see https://github.com/symfony/Validator/blob/master/Constraints/Url.php + */ + public static function url($value, $message = null, string $propertyPath = null): bool + { + static::string($value, $message, $propertyPath); + + $protocols = ['http', 'https']; + + $pattern = '~^ + (%s):// # protocol + (([\.\pL\pN-]+:)?([\.\pL\pN-]+)@)? # basic auth + ( + ([\pL\pN\pS\-\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name + | # or + \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address + | # or + \[ + (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::)))) + \] # an IPv6 address + ) + (:[0-9]+)? # a port (optional) + (?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%%[0-9A-Fa-f]{2})* )* # a path + (?:\? (?:[\pL\pN\-._\~!$&\'\[\]()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a query (optional) + (?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a fragment (optional) + $~ixu'; + + $pattern = \sprintf($pattern, \implode('|', $protocols)); + + if (!\preg_match($pattern, $value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" was expected to be a valid URL starting with http or https'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_URL, $propertyPath); + } + + return true; + } + + /** + * Assert that value is alphanumeric. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function alnum($value, $message = null, string $propertyPath = null): bool + { + try { + static::regex($value, '(^([a-zA-Z]{1}[a-zA-Z0-9]*)$)', $message, $propertyPath); + } catch (Throwable $e) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not alphanumeric, starting with letters and containing only letters and numbers.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_ALNUM, $propertyPath); + } + + return true; + } + + /** + * Assert that the value is boolean True. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function true($value, $message = null, string $propertyPath = null): bool + { + if (true !== $value) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not TRUE.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_TRUE, $propertyPath); + } + + return true; + } + + /** + * Assert that the value is boolean False. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function false($value, $message = null, string $propertyPath = null): bool + { + if (false !== $value) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not FALSE.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_FALSE, $propertyPath); + } + + return true; + } + + /** + * Assert that the class exists. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function classExists($value, $message = null, string $propertyPath = null): bool + { + if (!\class_exists($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Class "%s" does not exist.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_CLASS, $propertyPath); + } + + return true; + } + + /** + * Assert that the interface exists. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function interfaceExists($value, $message = null, string $propertyPath = null): bool + { + if (!\interface_exists($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Interface "%s" does not exist.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_INTERFACE, $propertyPath); + } + + return true; + } + + /** + * Assert that the class implements the interface. + * + * @param mixed $class + * @param string $interfaceName + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function implementsInterface($class, $interfaceName, $message = null, string $propertyPath = null): bool + { + try { + $reflection = new ReflectionClass($class); + if (!$reflection->implementsInterface($interfaceName)) { + $message = \sprintf( + static::generateMessage($message ?: 'Class "%s" does not implement interface "%s".'), + static::stringify($class), + static::stringify($interfaceName) + ); + + throw static::createException($class, $message, static::INTERFACE_NOT_IMPLEMENTED, $propertyPath, ['interface' => $interfaceName]); + } + } catch (ReflectionException $e) { + $message = \sprintf( + static::generateMessage($message ?: 'Class "%s" failed reflection.'), + static::stringify($class) + ); + throw static::createException($class, $message, static::INTERFACE_NOT_IMPLEMENTED, $propertyPath, ['interface' => $interfaceName]); + } + + return true; + } + + /** + * Assert that the given string is a valid json string. + * + * NOTICE: + * Since this does a json_decode to determine its validity + * you probably should consider, when using the variable + * content afterwards, just to decode and check for yourself instead + * of using this assertion. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function isJsonString($value, $message = null, string $propertyPath = null): bool + { + if (null === \json_decode($value) && JSON_ERROR_NONE !== \json_last_error()) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not a valid JSON string.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_JSON_STRING, $propertyPath); + } + + return true; + } + + /** + * Assert that the given string is a valid UUID. + * + * Uses code from {@link https://github.com/ramsey/uuid} that is MIT licensed. + * + * @param string $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function uuid($value, $message = null, string $propertyPath = null): bool + { + $value = \str_replace(['urn:', 'uuid:', '{', '}'], '', $value); + + if ('00000000-0000-0000-0000-000000000000' === $value) { + return true; + } + + if (!\preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not a valid UUID.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_UUID, $propertyPath); + } + + return true; + } + + /** + * Assert that the given string is a valid E164 Phone Number. + * + * @see https://en.wikipedia.org/wiki/E.164 + * + * @param string $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function e164($value, $message = null, string $propertyPath = null): bool + { + if (!\preg_match('/^\+?[1-9]\d{1,14}$/', $value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not a valid E164.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_E164, $propertyPath); + } + + return true; + } + + /** + * Assert that the count of countable is equal to count. + * + * @param array|Countable|ResourceBundle|SimpleXMLElement $countable + * @param int $count + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function count($countable, $count, $message = null, string $propertyPath = null): bool + { + if ($count !== \count($countable)) { + $message = \sprintf( + static::generateMessage($message ?: 'List does not contain exactly %d elements (%d given).'), + static::stringify($count), + static::stringify(\count($countable)) + ); + + throw static::createException($countable, $message, static::INVALID_COUNT, $propertyPath, ['count' => $count]); + } + + return true; + } + + /** + * Assert that the countable have at least $count elements. + * + * @param array|Countable|ResourceBundle|SimpleXMLElement $countable + * @param int $count + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function minCount($countable, $count, $message = null, string $propertyPath = null): bool + { + if ($count > \count($countable)) { + $message = \sprintf( + static::generateMessage($message ?: 'List should have at least %d elements, but has %d elements.'), + static::stringify($count), + static::stringify(\count($countable)) + ); + + throw static::createException($countable, $message, static::INVALID_MIN_COUNT, $propertyPath, ['count' => $count]); + } + + return true; + } + + /** + * Assert that the countable have at most $count elements. + * + * @param array|Countable|ResourceBundle|SimpleXMLElement $countable + * @param int $count + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function maxCount($countable, $count, $message = null, string $propertyPath = null): bool + { + if ($count < \count($countable)) { + $message = \sprintf( + static::generateMessage($message ?: 'List should have at most %d elements, but has %d elements.'), + static::stringify($count), + static::stringify(\count($countable)) + ); + + throw static::createException($countable, $message, static::INVALID_MAX_COUNT, $propertyPath, ['count' => $count]); + } + + return true; + } + + /** + * static call handler to implement: + * - "null or assertion" delegation + * - "all" delegation. + * + * @param string $method + * @param array $args + * + * @return bool|mixed + * + * @throws AssertionFailedException + */ + public static function __callStatic($method, $args) + { + if (0 === \strpos($method, 'nullOr')) { + if (!\array_key_exists(0, $args)) { + throw new BadMethodCallException('Missing the first argument.'); + } + + if (null === $args[0]) { + return true; + } + + $method = \substr($method, 6); + + return \call_user_func_array([\get_called_class(), $method], $args); + } + + if (0 === \strpos($method, 'all')) { + if (!\array_key_exists(0, $args)) { + throw new BadMethodCallException('Missing the first argument.'); + } + + static::isTraversable($args[0]); + + $method = \substr($method, 3); + $values = \array_shift($args); + $calledClass = \get_called_class(); + + foreach ($values as $value) { + \call_user_func_array([$calledClass, $method], \array_merge([$value], $args)); + } + + return true; + } + + throw new BadMethodCallException('No assertion Assertion#'.$method.' exists.'); + } + + /** + * Determines if the values array has every choice as key and that this choice has content. + * + * @param array $values + * @param array $choices + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function choicesNotEmpty(array $values, array $choices, $message = null, string $propertyPath = null): bool + { + static::notEmpty($values, $message, $propertyPath); + + foreach ($choices as $choice) { + static::notEmptyKey($values, $choice, $message, $propertyPath); + } + + return true; + } + + /** + * Determines that the named method is defined in the provided object. + * + * @param string $value + * @param mixed $object + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function methodExists($value, $object, $message = null, string $propertyPath = null): bool + { + static::isObject($object, $message, $propertyPath); + + if (!\method_exists($object, $value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Expected "%s" does not exist in provided object.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_METHOD, $propertyPath, ['object' => \get_class($object)]); + } + + return true; + } + + /** + * Determines that the provided value is an object. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function isObject($value, $message = null, string $propertyPath = null): bool + { + if (!\is_object($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Provided "%s" is not a valid object.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_OBJECT, $propertyPath); + } + + return true; + } + + /** + * Determines if the value is less than given limit. + * + * @param mixed $value + * @param mixed $limit + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function lessThan($value, $limit, $message = null, string $propertyPath = null): bool + { + if ($value >= $limit) { + $message = \sprintf( + static::generateMessage($message ?: 'Provided "%s" is not less than "%s".'), + static::stringify($value), + static::stringify($limit) + ); + + throw static::createException($value, $message, static::INVALID_LESS, $propertyPath, ['limit' => $limit]); + } + + return true; + } + + /** + * Determines if the value is less or equal than given limit. + * + * @param mixed $value + * @param mixed $limit + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function lessOrEqualThan($value, $limit, $message = null, string $propertyPath = null): bool + { + if ($value > $limit) { + $message = \sprintf( + static::generateMessage($message ?: 'Provided "%s" is not less or equal than "%s".'), + static::stringify($value), + static::stringify($limit) + ); + + throw static::createException($value, $message, static::INVALID_LESS_OR_EQUAL, $propertyPath, ['limit' => $limit]); + } + + return true; + } + + /** + * Determines if the value is greater than given limit. + * + * @param mixed $value + * @param mixed $limit + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function greaterThan($value, $limit, $message = null, string $propertyPath = null): bool + { + if ($value <= $limit) { + $message = \sprintf( + static::generateMessage($message ?: 'Provided "%s" is not greater than "%s".'), + static::stringify($value), + static::stringify($limit) + ); + + throw static::createException($value, $message, static::INVALID_GREATER, $propertyPath, ['limit' => $limit]); + } + + return true; + } + + /** + * Determines if the value is greater or equal than given limit. + * + * @param mixed $value + * @param mixed $limit + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function greaterOrEqualThan($value, $limit, $message = null, string $propertyPath = null): bool + { + if ($value < $limit) { + $message = \sprintf( + static::generateMessage($message ?: 'Provided "%s" is not greater or equal than "%s".'), + static::stringify($value), + static::stringify($limit) + ); + + throw static::createException($value, $message, static::INVALID_GREATER_OR_EQUAL, $propertyPath, ['limit' => $limit]); + } + + return true; + } + + /** + * Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit. + * + * @param mixed $value + * @param mixed $lowerLimit + * @param mixed $upperLimit + * @param string|callable|null $message + * @param string $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function between($value, $lowerLimit, $upperLimit, $message = null, string $propertyPath = null): bool + { + if ($lowerLimit > $value || $value > $upperLimit) { + $message = \sprintf( + static::generateMessage($message ?: 'Provided "%s" is neither greater than or equal to "%s" nor less than or equal to "%s".'), + static::stringify($value), + static::stringify($lowerLimit), + static::stringify($upperLimit) + ); + + throw static::createException($value, $message, static::INVALID_BETWEEN, $propertyPath, ['lower' => $lowerLimit, 'upper' => $upperLimit]); + } + + return true; + } + + /** + * Assert that a value is greater than a lower limit, and less than an upper limit. + * + * @param mixed $value + * @param mixed $lowerLimit + * @param mixed $upperLimit + * @param string|callable|null $message + * @param string $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function betweenExclusive($value, $lowerLimit, $upperLimit, $message = null, string $propertyPath = null): bool + { + if ($lowerLimit >= $value || $value >= $upperLimit) { + $message = \sprintf( + static::generateMessage($message ?: 'Provided "%s" is neither greater than "%s" nor less than "%s".'), + static::stringify($value), + static::stringify($lowerLimit), + static::stringify($upperLimit) + ); + + throw static::createException($value, $message, static::INVALID_BETWEEN_EXCLUSIVE, $propertyPath, ['lower' => $lowerLimit, 'upper' => $upperLimit]); + } + + return true; + } + + /** + * Assert that extension is loaded. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function extensionLoaded($value, $message = null, string $propertyPath = null): bool + { + if (!\extension_loaded($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Extension "%s" is required.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_EXTENSION, $propertyPath); + } + + return true; + } + + /** + * Assert that date is valid and corresponds to the given format. + * + * @param string $value + * @param string $format supports all of the options date(), except for the following: + * N, w, W, t, L, o, B, a, A, g, h, I, O, P, Z, c, r + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + * + * @see http://php.net/manual/function.date.php#refsect1-function.date-parameters + */ + public static function date($value, $format, $message = null, string $propertyPath = null): bool + { + static::string($value, $message, $propertyPath); + static::string($format, $message, $propertyPath); + + $dateTime = DateTime::createFromFormat('!'.$format, $value); + + if (false === $dateTime || $value !== $dateTime->format($format)) { + $message = \sprintf( + static::generateMessage($message ?: 'Date "%s" is invalid or does not match format "%s".'), + static::stringify($value), + static::stringify($format) + ); + + throw static::createException($value, $message, static::INVALID_DATE, $propertyPath, ['format' => $format]); + } + + return true; + } + + /** + * Assert that the value is an object, or a class that exists. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function objectOrClass($value, $message = null, string $propertyPath = null): bool + { + if (!\is_object($value)) { + static::classExists($value, $message, $propertyPath); + } + + return true; + } + + /** + * Assert that the value is an object or class, and that the property exists. + * + * @param mixed $value + * @param string $property + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function propertyExists($value, $property, $message = null, string $propertyPath = null): bool + { + static::objectOrClass($value); + + if (!\property_exists($value, $property)) { + $message = \sprintf( + static::generateMessage($message ?: 'Class "%s" does not have property "%s".'), + static::stringify($value), + static::stringify($property) + ); + + throw static::createException($value, $message, static::INVALID_PROPERTY, $propertyPath, ['property' => $property]); + } + + return true; + } + + /** + * Assert that the value is an object or class, and that the properties all exist. + * + * @param mixed $value + * @param array $properties + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function propertiesExist($value, array $properties, $message = null, string $propertyPath = null): bool + { + static::objectOrClass($value); + static::allString($properties, $message, $propertyPath); + + $invalidProperties = []; + foreach ($properties as $property) { + if (!\property_exists($value, $property)) { + $invalidProperties[] = $property; + } + } + + if ($invalidProperties) { + $message = \sprintf( + static::generateMessage($message ?: 'Class "%s" does not have these properties: %s.'), + static::stringify($value), + static::stringify(\implode(', ', $invalidProperties)) + ); + + throw static::createException($value, $message, static::INVALID_PROPERTY, $propertyPath, ['properties' => $properties]); + } + + return true; + } + + /** + * Assert comparison of two versions. + * + * @param string $version1 + * @param string $operator + * @param string $version2 + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function version($version1, $operator, $version2, $message = null, string $propertyPath = null): bool + { + static::notEmpty($operator, 'versionCompare operator is required and cannot be empty.'); + + if (true !== \version_compare($version1, $version2, $operator)) { + $message = \sprintf( + static::generateMessage($message ?: 'Version "%s" is not "%s" version "%s".'), + static::stringify($version1), + static::stringify($operator), + static::stringify($version2) + ); + + throw static::createException($version1, $message, static::INVALID_VERSION, $propertyPath, ['operator' => $operator, 'version' => $version2]); + } + + return true; + } + + /** + * Assert on PHP version. + * + * @param string $operator + * @param mixed $version + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function phpVersion($operator, $version, $message = null, string $propertyPath = null): bool + { + static::defined('PHP_VERSION'); + + return static::version(PHP_VERSION, $operator, $version, $message, $propertyPath); + } + + /** + * Assert that extension is loaded and a specific version is installed. + * + * @param string $extension + * @param string $operator + * @param mixed $version + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function extensionVersion($extension, $operator, $version, $message = null, string $propertyPath = null): bool + { + static::extensionLoaded($extension, $message, $propertyPath); + + return static::version(\phpversion($extension), $operator, $version, $message, $propertyPath); + } + + /** + * Determines that the provided value is callable. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function isCallable($value, $message = null, string $propertyPath = null): bool + { + if (!\is_callable($value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Provided "%s" is not a callable.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_CALLABLE, $propertyPath); + } + + return true; + } + + /** + * Assert that the provided value is valid according to a callback. + * + * If the callback returns `false` the assertion will fail. + * + * @param mixed $value + * @param callable $callback + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function satisfy($value, $callback, $message = null, string $propertyPath = null): bool + { + static::isCallable($callback); + + if (false === \call_user_func($callback, $value)) { + $message = \sprintf( + static::generateMessage($message ?: 'Provided "%s" is invalid according to custom rule.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_SATISFY, $propertyPath); + } + + return true; + } + + /** + * Assert that value is an IPv4 or IPv6 address + * (using input_filter/FILTER_VALIDATE_IP). + * + * @param string $value + * @param int|null $flag + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + * + * @see http://php.net/manual/filter.filters.flags.php + */ + public static function ip($value, $flag = null, $message = null, string $propertyPath = null): bool + { + static::string($value, $message, $propertyPath); + if (!\filter_var($value, FILTER_VALIDATE_IP, $flag)) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" was expected to be a valid IP address.'), + static::stringify($value) + ); + throw static::createException($value, $message, static::INVALID_IP, $propertyPath, ['flag' => $flag]); + } + + return true; + } + + /** + * Assert that value is an IPv4 address + * (using input_filter/FILTER_VALIDATE_IP). + * + * @param string $value + * @param int|null $flag + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + * + * @see http://php.net/manual/filter.filters.flags.php + */ + public static function ipv4($value, $flag = null, $message = null, string $propertyPath = null): bool + { + static::ip($value, $flag | FILTER_FLAG_IPV4, static::generateMessage($message ?: 'Value "%s" was expected to be a valid IPv4 address.'), $propertyPath); + + return true; + } + + /** + * Assert that value is an IPv6 address + * (using input_filter/FILTER_VALIDATE_IP). + * + * @param string $value + * @param int|null $flag + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + * + * @see http://php.net/manual/filter.filters.flags.php + */ + public static function ipv6($value, $flag = null, $message = null, string $propertyPath = null): bool + { + static::ip($value, $flag | FILTER_FLAG_IPV6, static::generateMessage($message ?: 'Value "%s" was expected to be a valid IPv6 address.'), $propertyPath); + + return true; + } + + /** + * Assert that a constant is defined. + * + * @param mixed $constant + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + */ + public static function defined($constant, $message = null, string $propertyPath = null): bool + { + if (!\defined($constant)) { + $message = \sprintf(static::generateMessage($message ?: 'Value "%s" expected to be a defined constant.'), $constant); + + throw static::createException($constant, $message, static::INVALID_CONSTANT, $propertyPath); + } + + return true; + } + + /** + * Assert that a constant is defined. + * + * @param string $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + * + * @throws AssertionFailedException + */ + public static function base64($value, $message = null, string $propertyPath = null): bool + { + if (false === \base64_decode($value, true)) { + $message = \sprintf(static::generateMessage($message ?: 'Value "%s" is not a valid base64 string.'), $value); + + throw static::createException($value, $message, static::INVALID_BASE64, $propertyPath); + } + + return true; + } + + /** + * Helper method that handles building the assertion failure exceptions. + * They are returned from this method so that the stack trace still shows + * the assertions method. + * + * @param mixed $value + * @param string|callable|null $message + * @param int $code + * @param string|null $propertyPath + * @param array $constraints + * + * @return mixed + */ + protected static function createException($value, $message, $code, $propertyPath = null, array $constraints = []) + { + $exceptionClass = static::$exceptionClass; + + return new $exceptionClass($message, $code, $propertyPath, $value, $constraints); + } + + /** + * Make a string version of a value. + * + * @param mixed $value + * + * @return string + */ + protected static function stringify($value): string + { + $result = \gettype($value); + + if (\is_bool($value)) { + $result = $value ? '' : ''; + } elseif (\is_scalar($value)) { + $val = (string)$value; + + if (\mb_strlen($val) > 100) { + $val = \mb_substr($val, 0, 97).'...'; + } + + $result = $val; + } elseif (\is_array($value)) { + $result = ''; + } elseif (\is_object($value)) { + $result = \get_class($value); + } elseif (\is_resource($value)) { + $result = \get_resource_type($value); + } elseif (null === $value) { + $result = ''; + } + + return $result; + } + + /** + * Generate the message. + * + * @param string|callable|null $message + * + * @return string + */ + protected static function generateMessage($message): string + { + if (\is_callable($message)) { + $traces = \debug_backtrace(0); + + $parameters = []; + + try { + $reflection = new ReflectionClass($traces[1]['class']); + $method = $reflection->getMethod($traces[1]['function']); + foreach ($method->getParameters() as $index => $parameter) { + if ('message' !== $parameter->getName()) { + $parameters[$parameter->getName()] = \array_key_exists($index, $traces[1]['args']) + ? $traces[1]['args'][$index] + : $parameter->getDefaultValue(); + } + } + + $parameters['::assertion'] = \sprintf('%s%s%s', $traces[1]['class'], $traces[1]['type'], $traces[1]['function']); + + $message = \call_user_func_array($message, [$parameters]); + } // @codeCoverageIgnoreStart + catch (Throwable $exception) { + $message = \sprintf('Unable to generate message : %s', $exception->getMessage()); + } // @codeCoverageIgnoreEnd + } + + return (string)$message; + } +} diff --git a/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/AssertionChain.php b/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/AssertionChain.php new file mode 100644 index 00000000..dbb6e839 --- /dev/null +++ b/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/AssertionChain.php @@ -0,0 +1,254 @@ + + * + * @method AssertionChain alnum(string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric. + * @method AssertionChain base64(string|callable $message = null, string $propertyPath = null) Assert that a constant is defined. + * @method AssertionChain between(mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit. + * @method AssertionChain betweenExclusive(mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater than a lower limit, and less than an upper limit. + * @method AssertionChain betweenLength(int $minLength, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string length is between min and max lengths. + * @method AssertionChain boolean(string|callable $message = null, string $propertyPath = null) Assert that value is php boolean. + * @method AssertionChain choice(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. + * @method AssertionChain choicesNotEmpty(array $choices, string|callable $message = null, string $propertyPath = null) Determines if the values array has every choice as key and that this choice has content. + * @method AssertionChain classExists(string|callable $message = null, string $propertyPath = null) Assert that the class exists. + * @method AssertionChain contains(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string contains a sequence of chars. + * @method AssertionChain count(int $count, string|callable $message = null, string $propertyPath = null) Assert that the count of countable is equal to count. + * @method AssertionChain date(string $format, string|callable $message = null, string $propertyPath = null) Assert that date is valid and corresponds to the given format. + * @method AssertionChain defined(string|callable $message = null, string $propertyPath = null) Assert that a constant is defined. + * @method AssertionChain digit(string|callable $message = null, string $propertyPath = null) Validates if an integer or integerish is a digit. + * @method AssertionChain directory(string|callable $message = null, string $propertyPath = null) Assert that a directory exists. + * @method AssertionChain e164(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid E164 Phone Number. + * @method AssertionChain email(string|callable $message = null, string $propertyPath = null) Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL). + * @method AssertionChain endsWith(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string ends with a sequence of chars. + * @method AssertionChain eq(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are equal (using ==). + * @method AssertionChain eqArraySubset(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that the array contains the subset. + * @method AssertionChain extensionLoaded(string|callable $message = null, string $propertyPath = null) Assert that extension is loaded. + * @method AssertionChain extensionVersion(string $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded and a specific version is installed. + * @method AssertionChain false(string|callable $message = null, string $propertyPath = null) Assert that the value is boolean False. + * @method AssertionChain file(string|callable $message = null, string $propertyPath = null) Assert that a file exists. + * @method AssertionChain float(string|callable $message = null, string $propertyPath = null) Assert that value is a php float. + * @method AssertionChain greaterOrEqualThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater or equal than given limit. + * @method AssertionChain greaterThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater than given limit. + * @method AssertionChain implementsInterface(string $interfaceName, string|callable $message = null, string $propertyPath = null) Assert that the class implements the interface. + * @method AssertionChain inArray(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. This is an alias of Assertion::choice(). + * @method AssertionChain integer(string|callable $message = null, string $propertyPath = null) Assert that value is a php integer. + * @method AssertionChain integerish(string|callable $message = null, string $propertyPath = null) Assert that value is a php integer'ish. + * @method AssertionChain interfaceExists(string|callable $message = null, string $propertyPath = null) Assert that the interface exists. + * @method AssertionChain ip(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 or IPv6 address. + * @method AssertionChain ipv4(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address. + * @method AssertionChain ipv6(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv6 address. + * @method AssertionChain isArray(string|callable $message = null, string $propertyPath = null) Assert that value is an array. + * @method AssertionChain isArrayAccessible(string|callable $message = null, string $propertyPath = null) Assert that value is an array or an array-accessible object. + * @method AssertionChain isCallable(string|callable $message = null, string $propertyPath = null) Determines that the provided value is callable. + * @method AssertionChain isCountable(string|callable $message = null, string $propertyPath = null) Assert that value is countable. + * @method AssertionChain isInstanceOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is instance of given class-name. + * @method AssertionChain isJsonString(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid json string. + * @method AssertionChain isObject(string|callable $message = null, string $propertyPath = null) Determines that the provided value is an object. + * @method AssertionChain isResource(string|callable $message = null, string $propertyPath = null) Assert that value is a resource. + * @method AssertionChain isTraversable(string|callable $message = null, string $propertyPath = null) Assert that value is an array or a traversable object. + * @method AssertionChain keyExists(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array. + * @method AssertionChain keyIsset(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object using isset(). + * @method AssertionChain keyNotExists(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key does not exist in an array. + * @method AssertionChain length(int $length, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string has a given length. + * @method AssertionChain lessOrEqualThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less or equal than given limit. + * @method AssertionChain lessThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less than given limit. + * @method AssertionChain max(mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that a number is smaller as a given limit. + * @method AssertionChain maxCount(int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at most $count elements. + * @method AssertionChain maxLength(int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string value is not longer than $maxLength chars. + * @method AssertionChain methodExists(mixed $object, string|callable $message = null, string $propertyPath = null) Determines that the named method is defined in the provided object. + * @method AssertionChain min(mixed $minValue, string|callable $message = null, string $propertyPath = null) Assert that a value is at least as big as a given limit. + * @method AssertionChain minCount(int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at least $count elements. + * @method AssertionChain minLength(int $minLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that a string is at least $minLength chars long. + * @method AssertionChain noContent(string|callable $message = null, string $propertyPath = null) Assert that value is empty. + * @method AssertionChain notBlank(string|callable $message = null, string $propertyPath = null) Assert that value is not blank. + * @method AssertionChain notContains(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string does not contains a sequence of chars. + * @method AssertionChain notEmpty(string|callable $message = null, string $propertyPath = null) Assert that value is not empty. + * @method AssertionChain notEmptyKey(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object and its value is not empty. + * @method AssertionChain notEq(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not equal (using ==). + * @method AssertionChain notInArray(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is not in array of choices. + * @method AssertionChain notIsInstanceOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is not instance of given class-name. + * @method AssertionChain notNull(string|callable $message = null, string $propertyPath = null) Assert that value is not null. + * @method AssertionChain notRegex(string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value does not match a regex. + * @method AssertionChain notSame(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not the same (using ===). + * @method AssertionChain null(string|callable $message = null, string $propertyPath = null) Assert that value is null. + * @method AssertionChain numeric(string|callable $message = null, string $propertyPath = null) Assert that value is numeric. + * @method AssertionChain objectOrClass(string|callable $message = null, string $propertyPath = null) Assert that the value is an object, or a class that exists. + * @method AssertionChain phpVersion(mixed $version, string|callable $message = null, string $propertyPath = null) Assert on PHP version. + * @method AssertionChain propertiesExist(array $properties, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the properties all exist. + * @method AssertionChain propertyExists(string $property, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the property exists. + * @method AssertionChain range(mixed $minValue, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that value is in range of numbers. + * @method AssertionChain readable(string|callable $message = null, string $propertyPath = null) Assert that the value is something readable. + * @method AssertionChain regex(string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value matches a regex. + * @method AssertionChain same(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are the same (using ===). + * @method AssertionChain satisfy(callable $callback, string|callable $message = null, string $propertyPath = null) Assert that the provided value is valid according to a callback. + * @method AssertionChain scalar(string|callable $message = null, string $propertyPath = null) Assert that value is a PHP scalar. + * @method AssertionChain startsWith(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string starts with a sequence of chars. + * @method AssertionChain string(string|callable $message = null, string $propertyPath = null) Assert that value is a string. + * @method AssertionChain subclassOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is subclass of given class-name. + * @method AssertionChain true(string|callable $message = null, string $propertyPath = null) Assert that the value is boolean True. + * @method AssertionChain url(string|callable $message = null, string $propertyPath = null) Assert that value is an URL. + * @method AssertionChain uuid(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid UUID. + * @method AssertionChain version(string $operator, string $version2, string|callable $message = null, string $propertyPath = null) Assert comparison of two versions. + * @method AssertionChain writeable(string|callable $message = null, string $propertyPath = null) Assert that the value is something writeable. + */ +class AssertionChain +{ + /** + * @var mixed + */ + private $value; + + /** + * @var string|callable|null + */ + private $defaultMessage; + + /** + * @var string|null + */ + private $defaultPropertyPath; + + /** + * Return each assertion as always valid. + * + * @var bool + */ + private $alwaysValid = false; + + /** + * Perform assertion on every element of array or traversable. + * + * @var bool + */ + private $all = false; + + /** @var string|Assertion Class to use for assertion calls */ + private $assertionClassName = 'Assert\Assertion'; + + /** + * AssertionChain constructor. + * + * @param mixed $value + * @param string|callable|null $defaultMessage + * @param string|null $defaultPropertyPath + */ + public function __construct($value, $defaultMessage = null, string $defaultPropertyPath = null) + { + $this->value = $value; + $this->defaultMessage = $defaultMessage; + $this->defaultPropertyPath = $defaultPropertyPath; + } + + /** + * Call assertion on the current value in the chain. + * + * @param string $methodName + * @param array $args + * + * @return AssertionChain + */ + public function __call($methodName, $args): AssertionChain + { + if (true === $this->alwaysValid) { + return $this; + } + + if (!\method_exists($this->assertionClassName, $methodName)) { + throw new \RuntimeException("Assertion '".$methodName."' does not exist."); + } + + $reflClass = new ReflectionClass($this->assertionClassName); + $method = $reflClass->getMethod($methodName); + + \array_unshift($args, $this->value); + $params = $method->getParameters(); + + foreach ($params as $idx => $param) { + if (isset($args[$idx])) { + continue; + } + + if ('message' == $param->getName()) { + $args[$idx] = $this->defaultMessage; + } + + if ('propertyPath' == $param->getName()) { + $args[$idx] = $this->defaultPropertyPath; + } + } + + if ($this->all) { + $methodName = 'all'.$methodName; + } + + \call_user_func_array([$this->assertionClassName, $methodName], $args); + + return $this; + } + + /** + * Switch chain into validation mode for an array of values. + * + * @return AssertionChain + */ + public function all(): AssertionChain + { + $this->all = true; + + return $this; + } + + /** + * Switch chain into mode allowing nulls, ignoring further assertions. + * + * @return AssertionChain + */ + public function nullOr(): AssertionChain + { + if (null === $this->value) { + $this->alwaysValid = true; + } + + return $this; + } + + /** + * @param string $className + * + * @return $this + */ + public function setAssertionClassName($className): AssertionChain + { + if (!\is_string($className)) { + throw new LogicException('Exception class name must be passed as a string'); + } + + if (Assertion::class !== $className && !\is_subclass_of($className, Assertion::class)) { + throw new LogicException($className.' is not (a subclass of) '.Assertion::class); + } + + $this->assertionClassName = $className; + + return $this; + } +} diff --git a/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/AssertionFailedException.php b/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/AssertionFailedException.php new file mode 100644 index 00000000..c1848388 --- /dev/null +++ b/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/AssertionFailedException.php @@ -0,0 +1,35 @@ +propertyPath = $propertyPath; + $this->value = $value; + $this->constraints = $constraints; + } + + /** + * User controlled way to define a sub-property causing + * the failure of a currently asserted objects. + * + * Useful to transport information about the nature of the error + * back to higher layers. + * + * @return string|null + */ + public function getPropertyPath() + { + return $this->propertyPath; + } + + /** + * Get the value that caused the assertion to fail. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * Get the constraints that applied to the failed assertion. + * + * @return array + */ + public function getConstraints(): array + { + return $this->constraints; + } +} diff --git a/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/LazyAssertion.php b/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/LazyAssertion.php new file mode 100644 index 00000000..c032911a --- /dev/null +++ b/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/LazyAssertion.php @@ -0,0 +1,230 @@ + + * + * @method static static alnum(string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric. + * @method static static base64(string|callable $message = null, string $propertyPath = null) Assert that a constant is defined. + * @method static static between(mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit. + * @method static static betweenExclusive(mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater than a lower limit, and less than an upper limit. + * @method static static betweenLength(int $minLength, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string length is between min and max lengths. + * @method static static boolean(string|callable $message = null, string $propertyPath = null) Assert that value is php boolean. + * @method static static choice(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. + * @method static static choicesNotEmpty(array $choices, string|callable $message = null, string $propertyPath = null) Determines if the values array has every choice as key and that this choice has content. + * @method static static classExists(string|callable $message = null, string $propertyPath = null) Assert that the class exists. + * @method static static contains(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string contains a sequence of chars. + * @method static static count(int $count, string|callable $message = null, string $propertyPath = null) Assert that the count of countable is equal to count. + * @method static static date(string $format, string|callable $message = null, string $propertyPath = null) Assert that date is valid and corresponds to the given format. + * @method static static defined(string|callable $message = null, string $propertyPath = null) Assert that a constant is defined. + * @method static static digit(string|callable $message = null, string $propertyPath = null) Validates if an integer or integerish is a digit. + * @method static static directory(string|callable $message = null, string $propertyPath = null) Assert that a directory exists. + * @method static static e164(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid E164 Phone Number. + * @method static static email(string|callable $message = null, string $propertyPath = null) Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL). + * @method static static endsWith(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string ends with a sequence of chars. + * @method static static eq(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are equal (using ==). + * @method static static eqArraySubset(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that the array contains the subset. + * @method static static extensionLoaded(string|callable $message = null, string $propertyPath = null) Assert that extension is loaded. + * @method static static extensionVersion(string $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded and a specific version is installed. + * @method static static false(string|callable $message = null, string $propertyPath = null) Assert that the value is boolean False. + * @method static static file(string|callable $message = null, string $propertyPath = null) Assert that a file exists. + * @method static static float(string|callable $message = null, string $propertyPath = null) Assert that value is a php float. + * @method static static greaterOrEqualThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater or equal than given limit. + * @method static static greaterThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater than given limit. + * @method static static implementsInterface(string $interfaceName, string|callable $message = null, string $propertyPath = null) Assert that the class implements the interface. + * @method static static inArray(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. This is an alias of Assertion::choice(). + * @method static static integer(string|callable $message = null, string $propertyPath = null) Assert that value is a php integer. + * @method static static integerish(string|callable $message = null, string $propertyPath = null) Assert that value is a php integer'ish. + * @method static static interfaceExists(string|callable $message = null, string $propertyPath = null) Assert that the interface exists. + * @method static static ip(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 or IPv6 address. + * @method static static ipv4(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address. + * @method static static ipv6(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv6 address. + * @method static static isArray(string|callable $message = null, string $propertyPath = null) Assert that value is an array. + * @method static static isArrayAccessible(string|callable $message = null, string $propertyPath = null) Assert that value is an array or an array-accessible object. + * @method static static isCallable(string|callable $message = null, string $propertyPath = null) Determines that the provided value is callable. + * @method static static isCountable(string|callable $message = null, string $propertyPath = null) Assert that value is countable. + * @method static static isInstanceOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is instance of given class-name. + * @method static static isJsonString(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid json string. + * @method static static isObject(string|callable $message = null, string $propertyPath = null) Determines that the provided value is an object. + * @method static static isResource(string|callable $message = null, string $propertyPath = null) Assert that value is a resource. + * @method static static isTraversable(string|callable $message = null, string $propertyPath = null) Assert that value is an array or a traversable object. + * @method static static keyExists(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array. + * @method static static keyIsset(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object using isset(). + * @method static static keyNotExists(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key does not exist in an array. + * @method static static length(int $length, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string has a given length. + * @method static static lessOrEqualThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less or equal than given limit. + * @method static static lessThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less than given limit. + * @method static static max(mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that a number is smaller as a given limit. + * @method static static maxCount(int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at most $count elements. + * @method static static maxLength(int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string value is not longer than $maxLength chars. + * @method static static methodExists(mixed $object, string|callable $message = null, string $propertyPath = null) Determines that the named method is defined in the provided object. + * @method static static min(mixed $minValue, string|callable $message = null, string $propertyPath = null) Assert that a value is at least as big as a given limit. + * @method static static minCount(int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at least $count elements. + * @method static static minLength(int $minLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that a string is at least $minLength chars long. + * @method static static noContent(string|callable $message = null, string $propertyPath = null) Assert that value is empty. + * @method static static notBlank(string|callable $message = null, string $propertyPath = null) Assert that value is not blank. + * @method static static notContains(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string does not contains a sequence of chars. + * @method static static notEmpty(string|callable $message = null, string $propertyPath = null) Assert that value is not empty. + * @method static static notEmptyKey(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object and its value is not empty. + * @method static static notEq(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not equal (using ==). + * @method static static notInArray(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is not in array of choices. + * @method static static notIsInstanceOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is not instance of given class-name. + * @method static static notNull(string|callable $message = null, string $propertyPath = null) Assert that value is not null. + * @method static static notRegex(string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value does not match a regex. + * @method static static notSame(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not the same (using ===). + * @method static static null(string|callable $message = null, string $propertyPath = null) Assert that value is null. + * @method static static numeric(string|callable $message = null, string $propertyPath = null) Assert that value is numeric. + * @method static static objectOrClass(string|callable $message = null, string $propertyPath = null) Assert that the value is an object, or a class that exists. + * @method static static phpVersion(mixed $version, string|callable $message = null, string $propertyPath = null) Assert on PHP version. + * @method static static propertiesExist(array $properties, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the properties all exist. + * @method static static propertyExists(string $property, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the property exists. + * @method static static range(mixed $minValue, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that value is in range of numbers. + * @method static static readable(string|callable $message = null, string $propertyPath = null) Assert that the value is something readable. + * @method static static regex(string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value matches a regex. + * @method static static same(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are the same (using ===). + * @method static static satisfy(callable $callback, string|callable $message = null, string $propertyPath = null) Assert that the provided value is valid according to a callback. + * @method static static scalar(string|callable $message = null, string $propertyPath = null) Assert that value is a PHP scalar. + * @method static static startsWith(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string starts with a sequence of chars. + * @method static static string(string|callable $message = null, string $propertyPath = null) Assert that value is a string. + * @method static static subclassOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is subclass of given class-name. + * @method static static true(string|callable $message = null, string $propertyPath = null) Assert that the value is boolean True. + * @method static static url(string|callable $message = null, string $propertyPath = null) Assert that value is an URL. + * @method static static uuid(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid UUID. + * @method static static version(string $operator, string $version2, string|callable $message = null, string $propertyPath = null) Assert comparison of two versions. + * @method static static writeable(string|callable $message = null, string $propertyPath = null) Assert that the value is something writeable. + * @method static static all() Switch chain into validation mode for an array of values. + * @method static static nullOr() Switch chain into mode allowing nulls, ignoring further assertions. + */ +class LazyAssertion +{ + private $currentChainFailed = false; + private $alwaysTryAll = false; + private $thisChainTryAll = false; + private $currentChain; + private $errors = []; + + /** @var string The class to use as AssertionChain factory */ + private $assertClass = Assert::class; + + /** @var string|LazyAssertionException The class to use for exceptions */ + private $exceptionClass = LazyAssertionException::class; + + /** + * @param mixed $value + * @param string|null $propertyPath + * @param string|callable|null $defaultMessage + * + * @return static + */ + public function that($value, string $propertyPath = null, $defaultMessage = null) + { + $this->currentChainFailed = false; + $this->thisChainTryAll = false; + $assertClass = $this->assertClass; + $this->currentChain = $assertClass::that($value, $defaultMessage, $propertyPath); + + return $this; + } + + /** + * @return static + */ + public function tryAll() + { + if (!$this->currentChain) { + $this->alwaysTryAll = true; + } + + $this->thisChainTryAll = true; + + return $this; + } + + /** + * @param string $method + * @param array $args + * + * @return static + */ + public function __call($method, $args) + { + if (false === $this->alwaysTryAll + && false === $this->thisChainTryAll + && true === $this->currentChainFailed + ) { + return $this; + } + + try { + \call_user_func_array([$this->currentChain, $method], $args); + } catch (AssertionFailedException $e) { + $this->errors[] = $e; + $this->currentChainFailed = true; + } + + return $this; + } + + /** + * @return bool + * + * @throws LazyAssertionException + */ + public function verifyNow(): bool + { + if ($this->errors) { + throw \call_user_func([$this->exceptionClass, 'fromErrors'], $this->errors); + } + + return true; + } + + /** + * @param string $className + * + * @return static + */ + public function setAssertClass(string $className) + { + if (Assert::class !== $className && !\is_subclass_of($className, Assert::class)) { + throw new LogicException($className.' is not (a subclass of) '.Assert::class); + } + + $this->assertClass = $className; + + return $this; + } + + /** + * @param string $className + * + * @return static + */ + public function setExceptionClass(string $className) + { + if (LazyAssertionException::class !== $className && !\is_subclass_of($className, LazyAssertionException::class)) { + throw new LogicException($className.' is not (a subclass of) '.LazyAssertionException::class); + } + + $this->exceptionClass = $className; + + return $this; + } +} diff --git a/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/LazyAssertionException.php b/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/LazyAssertionException.php new file mode 100644 index 00000000..f76ecc69 --- /dev/null +++ b/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/LazyAssertionException.php @@ -0,0 +1,55 @@ +getPropertyPath(), $error->getMessage()); + } + + return new static($message, $errors); + } + + public function __construct($message, array $errors) + { + parent::__construct($message, 0, null, null); + + $this->errors = $errors; + } + + /** + * @return InvalidArgumentException[] + */ + public function getErrorExceptions(): array + { + return $this->errors; + } +} diff --git a/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/functions.php b/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/functions.php new file mode 100644 index 00000000..e0ccbd7d --- /dev/null +++ b/lam/lib/3rdParty/composer/beberlei/assert/lib/Assert/functions.php @@ -0,0 +1,80 @@ +notEmpty()->integer(); + * \Assert\that($value)->nullOr()->string()->startsWith("Foo"); + * + * The assertion chain can be stateful, that means be careful when you reuse + * it. You should never pass around the chain. + */ +function that($value, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain +{ + return Assert::that($value, $defaultMessage, $defaultPropertyPath); +} + +/** + * Start validation on a set of values, returns {@link AssertionChain}. + * + * @param mixed $values + * @param string|callable|null $defaultMessage + * @param string $defaultPropertyPath + * + * @return AssertionChain + */ +function thatAll($values, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain +{ + return Assert::thatAll($values, $defaultMessage, $defaultPropertyPath); +} + +/** + * Start validation and allow NULL, returns {@link AssertionChain}. + * + * @param mixed $value + * @param string|callable|null $defaultMessage + * @param string $defaultPropertyPath + * + * @return AssertionChain + * + * @deprecated In favour of Assert::thatNullOr($value, $defaultMessage = null, $defaultPropertyPath = null) + */ +function thatNullOr($value, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain +{ + return Assert::thatNullOr($value, $defaultMessage, $defaultPropertyPath); +} + +/** + * Create a lazy assertion object. + * + * @return LazyAssertion + */ +function lazy(): LazyAssertion +{ + return Assert::lazy(); +} diff --git a/lam/lib/3rdParty/composer/beberlei/assert/phpstan-code.neon b/lam/lib/3rdParty/composer/beberlei/assert/phpstan-code.neon new file mode 100644 index 00000000..49aa5723 --- /dev/null +++ b/lam/lib/3rdParty/composer/beberlei/assert/phpstan-code.neon @@ -0,0 +1,10 @@ +parameters: + autoload_files: + - bin/MethodDocGenerator.php + ignoreErrors: + # is_countable() is available only in PHP 7.3+ + - '#Function is_countable not found#' + + # Calling count() on ResourceBundle and SimpleXMLElement is valid from PHP 7.0+, but does not correctly advertise + # the fact, and so PHPStan has an issue with this. This will be fixed in PHP 7.4+ + - '#Call to function count\(\) with argument type array|Countable|ResourceBundle|SimpleXMLElement will always result in number 1#' diff --git a/lam/lib/3rdParty/composer/beberlei/assert/phpstan-tests.neon b/lam/lib/3rdParty/composer/beberlei/assert/phpstan-tests.neon new file mode 100644 index 00000000..e9ab0bcf --- /dev/null +++ b/lam/lib/3rdParty/composer/beberlei/assert/phpstan-tests.neon @@ -0,0 +1,10 @@ +parameters: + ignoreErrors: + # The following errors are ignored as they are testing for errors and exceptions that static analysis correctly identifies as problems. + - '#Call to an undefined method Assert\\AssertionChain::unknownAssertion\(\)#' + - '#Call to an undefined static method Assert\\Assertion::nullOrAssertionDoesNotExist\(\)#' + - '#Class Foo not found#' + - '#Parameter \#1 $value of static method Assert\\Assertion::isCountable\(\) expects array|Countable|ResourceBundle|SimpleXMLElement, string given#' + - '#Parameter \#2 \$operator of static method Assert\\Assertion::version\(\) expects string, null given#' + - '#Static method Assert\\Assertion::allTrue\(\) invoked with 0 parameters, 1-3 required#' + - '#Static method Assert\\Assertion::nullOrMax\(\) invoked with 0 parameters, 2-4 required#' diff --git a/lam/lib/3rdParty/composer/composer/ClassLoader.php b/lam/lib/3rdParty/composer/composer/ClassLoader.php new file mode 100644 index 00000000..fce8549f --- /dev/null +++ b/lam/lib/3rdParty/composer/composer/ClassLoader.php @@ -0,0 +1,445 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/lam/lib/3rdParty/composer/composer/LICENSE b/lam/lib/3rdParty/composer/composer/LICENSE new file mode 100644 index 00000000..4b615a38 --- /dev/null +++ b/lam/lib/3rdParty/composer/composer/LICENSE @@ -0,0 +1,56 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: Composer +Upstream-Contact: Jordi Boggiano +Source: https://github.com/composer/composer + +Files: * +Copyright: 2016, Nils Adermann + 2016, Jordi Boggiano +License: Expat + +Files: src/Composer/Util/TlsHelper.php +Copyright: 2016, Nils Adermann + 2016, Jordi Boggiano + 2013, Evan Coury +License: Expat and BSD-2-Clause + +License: BSD-2-Clause + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + . + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + . + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License: Expat + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is furnished + to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/lam/lib/3rdParty/composer/composer/autoload_classmap.php b/lam/lib/3rdParty/composer/composer/autoload_classmap.php new file mode 100644 index 00000000..024622f7 --- /dev/null +++ b/lam/lib/3rdParty/composer/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + $vendorDir . '/beberlei/assert/lib/Assert/functions.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', +); diff --git a/lam/lib/3rdParty/composer/composer/autoload_namespaces.php b/lam/lib/3rdParty/composer/composer/autoload_namespaces.php new file mode 100644 index 00000000..9bd4acee --- /dev/null +++ b/lam/lib/3rdParty/composer/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/web-auth/metadata-service/src'), + 'Webauthn\\' => array($vendorDir . '/web-auth/webauthn-lib/src'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), + 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), + 'Nyholm\\Psr7\\' => array($vendorDir . '/nyholm/psr7/src'), + 'Http\\Message\\' => array($vendorDir . '/php-http/message-factory/src'), + 'FG\\' => array($vendorDir . '/fgrosse/phpasn1/lib'), + 'Cose\\' => array($vendorDir . '/web-auth/cose-lib/src'), + 'CBOR\\' => array($vendorDir . '/spomky-labs/cbor-php/src'), + 'Base64Url\\' => array($vendorDir . '/spomky-labs/base64url/src'), + 'Assert\\' => array($vendorDir . '/beberlei/assert/lib/Assert'), +); diff --git a/lam/lib/3rdParty/composer/composer/autoload_real.php b/lam/lib/3rdParty/composer/composer/autoload_real.php new file mode 100644 index 00000000..9fc69849 --- /dev/null +++ b/lam/lib/3rdParty/composer/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequireed73ceb9c1bdec18b7c6d09764d1bce5($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequireed73ceb9c1bdec18b7c6d09764d1bce5($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/lam/lib/3rdParty/composer/composer/autoload_static.php b/lam/lib/3rdParty/composer/composer/autoload_static.php new file mode 100644 index 00000000..5659a0d0 --- /dev/null +++ b/lam/lib/3rdParty/composer/composer/autoload_static.php @@ -0,0 +1,124 @@ + __DIR__ . '/..' . '/beberlei/assert/lib/Assert/functions.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'W' => + array ( + 'Webauthn\\MetadataService\\' => 25, + 'Webauthn\\' => 9, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Ctype\\' => 23, + ), + 'R' => + array ( + 'Ramsey\\Uuid\\' => 12, + ), + 'P' => + array ( + 'Psr\\Http\\Message\\' => 17, + 'Psr\\Http\\Client\\' => 16, + ), + 'N' => + array ( + 'Nyholm\\Psr7\\' => 12, + ), + 'H' => + array ( + 'Http\\Message\\' => 13, + ), + 'F' => + array ( + 'FG\\' => 3, + ), + 'C' => + array ( + 'Cose\\' => 5, + 'CBOR\\' => 5, + ), + 'B' => + array ( + 'Base64Url\\' => 10, + ), + 'A' => + array ( + 'Assert\\' => 7, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Webauthn\\MetadataService\\' => + array ( + 0 => __DIR__ . '/..' . '/web-auth/metadata-service/src', + ), + 'Webauthn\\' => + array ( + 0 => __DIR__ . '/..' . '/web-auth/webauthn-lib/src', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Ramsey\\Uuid\\' => + array ( + 0 => __DIR__ . '/..' . '/ramsey/uuid/src', + ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-factory/src', + 1 => __DIR__ . '/..' . '/psr/http-message/src', + ), + 'Psr\\Http\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-client/src', + ), + 'Nyholm\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/nyholm/psr7/src', + ), + 'Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/php-http/message-factory/src', + ), + 'FG\\' => + array ( + 0 => __DIR__ . '/..' . '/fgrosse/phpasn1/lib', + ), + 'Cose\\' => + array ( + 0 => __DIR__ . '/..' . '/web-auth/cose-lib/src', + ), + 'CBOR\\' => + array ( + 0 => __DIR__ . '/..' . '/spomky-labs/cbor-php/src', + ), + 'Base64Url\\' => + array ( + 0 => __DIR__ . '/..' . '/spomky-labs/base64url/src', + ), + 'Assert\\' => + array ( + 0 => __DIR__ . '/..' . '/beberlei/assert/lib/Assert', + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5::$prefixDirsPsr4; + + }, null, ClassLoader::class); + } +} diff --git a/lam/lib/3rdParty/composer/composer/installed.json b/lam/lib/3rdParty/composer/composer/installed.json new file mode 100644 index 00000000..40511296 --- /dev/null +++ b/lam/lib/3rdParty/composer/composer/installed.json @@ -0,0 +1,915 @@ +[ + { + "name": "beberlei/assert", + "version": "v3.2.6", + "version_normalized": "3.2.6.0", + "source": { + "type": "git", + "url": "https://github.com/beberlei/assert.git", + "reference": "99508be011753690fe108ded450f5caaae180cfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beberlei/assert/zipball/99508be011753690fe108ded450f5caaae180cfa", + "reference": "99508be011753690fe108ded450f5caaae180cfa", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "php": "^7" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "phpstan/phpstan-shim": "*", + "phpunit/phpunit": ">=6.0.0 <8" + }, + "suggest": { + "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" + }, + "time": "2019-10-10T10:33:57+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Assert\\": "lib/Assert" + }, + "files": [ + "lib/Assert/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de", + "role": "Lead Developer" + }, + { + "name": "Richard Quadling", + "email": "rquadling@gmail.com", + "role": "Collaborator" + } + ], + "description": "Thin assertion library for input validation in business models.", + "keywords": [ + "assert", + "assertion", + "validation" + ] + }, + { + "name": "fgrosse/phpasn1", + "version": "v2.1.1", + "version_normalized": "2.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/fgrosse/PHPASN1.git", + "reference": "7ebf2a09084a7bbdb7b879c66fdf7ad80461bbe8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/7ebf2a09084a7bbdb7b879c66fdf7ad80461bbe8", + "reference": "7ebf2a09084a7bbdb7b879c66fdf7ad80461bbe8", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.3", + "satooshi/php-coveralls": "~2.0" + }, + "suggest": { + "ext-gmp": "GMP is the preferred extension for big integer calculations", + "php-curl": "For loading OID information from the web if they have not bee defined statically" + }, + "time": "2018-12-02T01:34:34+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "FG\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Friedrich Große", + "email": "friedrich.grosse@gmail.com", + "homepage": "https://github.com/FGrosse", + "role": "Author" + }, + { + "name": "All contributors", + "homepage": "https://github.com/FGrosse/PHPASN1/contributors" + } + ], + "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.", + "homepage": "https://github.com/FGrosse/PHPASN1", + "keywords": [ + "DER", + "asn.1", + "asn1", + "ber", + "binary", + "decoding", + "encoding", + "x.509", + "x.690", + "x509", + "x690" + ] + }, + { + "name": "nyholm/psr7", + "version": "1.2.1", + "version_normalized": "1.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "55ff6b76573f5b242554c9775792bd59fb52e11c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/55ff6b76573f5b242554c9775792bd59fb52e11c", + "reference": "55ff6b76573f5b242554c9775792bd59fb52e11c", + "shasum": "" + }, + "require": { + "php": "^7.1", + "php-http/message-factory": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "dev-master", + "php-http/psr7-integration-tests": "dev-master", + "phpunit/phpunit": "^7.5" + }, + "time": "2019-09-05T13:24:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "http://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ] + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "version_normalized": "9.99.99.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "time": "2018-07-02T15:55:56+00:00", + "type": "library", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ] + }, + { + "name": "php-http/message-factory", + "version": "v1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/message-factory.git", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0" + }, + "time": "2015-12-19T14:08:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Factory interfaces for PSR-7 HTTP Message", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "stream", + "uri" + ] + }, + { + "name": "psr/http-client", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "496a823ef742b632934724bf769560c2a5c7c44e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/496a823ef742b632934724bf769560c2a5c7c44e", + "reference": "496a823ef742b632934724bf769560c2a5c7c44e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "psr/http-message": "^1.0" + }, + "time": "2018-10-30T23:29:13+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ] + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "time": "2019-04-30T12:38:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ] + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06T14:39:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ] + }, + { + "name": "ramsey/uuid", + "version": "3.8.0", + "version_normalized": "3.8.0.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/d09ea80159c1929d75b3f9c60504d613aeb4a1e3", + "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "^1.0|^2.0|9.99.99", + "php": "^5.4 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^1.0 | ~2.0.0", + "doctrine/annotations": "~1.2.0", + "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ~2.1.0", + "ircmaxell/random-lib": "^1.1", + "jakub-onderka/php-parallel-lint": "^0.9.0", + "mockery/mockery": "^0.9.9", + "moontoast/math": "^1.1", + "php-mock/php-mock-phpunit": "^0.3|^1.1", + "phpunit/phpunit": "^4.7|^5.0|^6.5", + "squizlabs/php_codesniffer": "^2.3" + }, + "suggest": { + "ext-ctype": "Provides support for PHP Ctype functions", + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "time": "2018-07-19T23:38:55+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + }, + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ] + }, + { + "name": "spomky-labs/base64url", + "version": "v2.0.1", + "version_normalized": "2.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/base64url.git", + "reference": "3eb46a1de803f0078962d910e3a2759224a68c61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/3eb46a1de803f0078962d910e3a2759224a68c61", + "reference": "3eb46a1de803f0078962d910e3a2759224a68c61", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^7.0" + }, + "time": "2018-08-16T15:44:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Base64Url\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky-Labs/base64url/contributors" + } + ], + "description": "Base 64 URL Safe Encoding/Decoding PHP Library", + "homepage": "https://github.com/Spomky-Labs/base64url", + "keywords": [ + "base64", + "rfc4648", + "safe", + "url" + ] + }, + { + "name": "spomky-labs/cbor-php", + "version": "v1.0.7", + "version_normalized": "1.0.7.0", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/cbor-php.git", + "reference": "f937d527b05bb3ec3f6b8670ece38ccaf9a0eddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/f937d527b05bb3ec3f6b8670ece38ccaf9a0eddd", + "reference": "f937d527b05bb3ec3f6b8670ece38ccaf9a0eddd", + "shasum": "" + }, + "require": { + "beberlei/assert": "^3.2", + "ext-gmp": "*", + "php": "^7.1|^8.0", + "spomky-labs/base64url": "^1.0|^2.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-beberlei-assert": "^0.11.0", + "phpstan/phpstan-deprecation-rules": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11", + "phpunit/phpunit": "^7.5|^8.0", + "rector/rector": "^0.5" + }, + "suggest": { + "ext-bcmath": "BCMath extension needed to handle the Big Float and Decimal Fraction Tags" + }, + "time": "2019-08-15T14:53:55+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "CBOR\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/cbor-php/contributors" + } + ], + "description": "CBOR Encoder/Decoder for PHP", + "keywords": [ + "Concise Binary Object Representation", + "RFC7049", + "cbor" + ] + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.12.0", + "version_normalized": "1.12.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2019-08-06T08:03:45+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ] + }, + { + "name": "web-auth/cose-lib", + "version": "v2.1.7", + "version_normalized": "2.1.7.0", + "source": { + "type": "git", + "url": "https://github.com/web-auth/cose-lib.git", + "reference": "8d1c37bac6e5db8d502b7735448d416f05fb4c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/8d1c37bac6e5db8d502b7735448d416f05fb4c70", + "reference": "8d1c37bac6e5db8d502b7735448d416f05fb4c70", + "shasum": "" + }, + "require": { + "beberlei/assert": "^3.0", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "fgrosse/phpasn1": "^2.1", + "php": "^7.2" + }, + "time": "2019-09-04T20:53:12+00:00", + "type": "library", + "extra": { + "branch-alias": { + "v1.0": "1.0.x-dev", + "v1.1": "1.1.x-dev", + "v1.2": "1.2.x-dev", + "v2.0": "2.0.x-dev", + "v2.1": "2.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Cose\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/cose/contributors" + } + ], + "description": "CBOR Object Signing and Encryption (COSE) For PHP", + "homepage": "https://github.com/web-auth", + "keywords": [ + "COSE", + "RFC8152" + ] + }, + { + "name": "web-auth/metadata-service", + "version": "v2.1.7", + "version_normalized": "2.1.7.0", + "source": { + "type": "git", + "url": "https://github.com/web-auth/webauthn-metadata-service.git", + "reference": "5fc754d00dfa05913260dc3781227dfa8ed7dbdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/5fc754d00dfa05913260dc3781227dfa8ed7dbdd", + "reference": "5fc754d00dfa05913260dc3781227dfa8ed7dbdd", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.1", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "time": "2019-09-04T20:53:12+00:00", + "type": "library", + "extra": { + "branch-alias": { + "v2.1": "2.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Webauthn\\MetadataService\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/metadata-service/contributors" + } + ], + "description": "Metadata Service for FIDO2/Webauthn", + "homepage": "https://github.com/web-auth", + "keywords": [ + "FIDO2", + "fido", + "webauthn" + ] + }, + { + "name": "web-auth/webauthn-lib", + "version": "v2.1.7", + "version_normalized": "2.1.7.0", + "source": { + "type": "git", + "url": "https://github.com/web-auth/webauthn-lib.git", + "reference": "4cd346f2ef4d282296e503b7b1b3ef200347437b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/4cd346f2ef4d282296e503b7b1b3ef200347437b", + "reference": "4cd346f2ef4d282296e503b7b1b3ef200347437b", + "shasum": "" + }, + "require": { + "beberlei/assert": "^3.0", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "nyholm/psr7": "^1.1", + "php": "^7.2", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ramsey/uuid": "^3.8", + "spomky-labs/base64url": "^2.0", + "spomky-labs/cbor-php": "^1.0.2", + "web-auth/cose-lib": "self.version", + "web-auth/metadata-service": "self.version" + }, + "suggest": { + "web-token/jwt-signature-algorithm-ecdsa": "Recommended for the AndroidSafetyNet Attestation Statement support", + "web-token/jwt-signature-algorithm-eddsa": "Recommended for the AndroidSafetyNet Attestation Statement support", + "web-token/jwt-signature-algorithm-rsa": "Mandatory for the AndroidSafetyNet Attestation Statement support" + }, + "time": "2019-09-09T12:04:09+00:00", + "type": "library", + "extra": { + "branch-alias": { + "v1.0": "1.0.x-dev", + "v1.1": "1.1.x-dev", + "v1.2": "1.2.x-dev", + "v2.0": "2.0.x-dev", + "v2.1": "2.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Webauthn\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/webauthn-library/contributors" + } + ], + "description": "FIDO2/Webauthn Support For PHP", + "homepage": "https://github.com/web-auth", + "keywords": [ + "FIDO2", + "fido", + "webauthn" + ] + } +] diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/CHANGELOG.md b/lam/lib/3rdParty/composer/fgrosse/phpasn1/CHANGELOG.md new file mode 100644 index 00000000..053edc2d --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/CHANGELOG.md @@ -0,0 +1,38 @@ +#### v.2.1.0 (2018-03) +* add support for `bcmath` extension (making `gmp` optional)
+ https://github.com/fgrosse/PHPASN1/pull/68 + +#### v.2.0.1 & v.1.5.3 (2017-12) +* add .gitattributes file to prevent examples and tests to be installed via composer when --prefer-dist was set + +#### v.2.0.0 (2017-08) +* rename `FG\ASN1\Object` to `FG\ASN1\ASNObject` because `Object` is a special class name in the next major PHP release + - when you upgrade you have to adapt all corresponding `use` and `extends` statements as well as type hints and all + usages of `Object::fromBinary(…)`. +* generally drop PHP 5.6 support + +#### v.1.5.2 (2016-10-29) +* allow empty octet strings + +#### v.1.5.1 (2015-10-02) +* add keywords to composer.json (this is a version on its own so the keywords are found on a stable version at packagist.org) + +#### v.1.5.0 (2015-10-30) +* fix a bug that would prevent you from decoding context specific tags on multiple objects [#57](https://github.com/fgrosse/PHPASN1/issues/57) + - `ExplicitlyTaggedObject::__construct` does now accept multiple objects to be tagged with a single tag + - `ExplicitlyTaggedObject::getContent` will now always return an array (even if only one object is tagged) + +#### v.1.4.2 (2015-09-29) +* fix a bug that would prevent you from decoding empty tagged objects [#57](https://github.com/fgrosse/PHPASN1/issues/57) + +#### v.1.4.1 +* improve exception messages and general error handling [#55](https://github.com/fgrosse/PHPASN1/pull/55) + +#### v.1.4.0 +* **require PHP 5.6** +* support big integers (closes #1 and #37) +* enforce one code style via [styleci.io][9] +* track code coverage via [coveralls.io][10] +* replace obsolete `FG\ASN1\Exception\GeneralException` with `\Exception` +* `Construct` (`Sequence`, `Set`) does now implement `ArrayAccess`, `Countable` and `Iterator` so its easier to use +* add [`TemplateParser`][11] diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/LICENSE b/lam/lib/3rdParty/composer/fgrosse/phpasn1/LICENSE new file mode 100644 index 00000000..1e17eb03 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012-2015 Friedrich Große + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/README.md b/lam/lib/3rdParty/composer/fgrosse/phpasn1/README.md new file mode 100644 index 00000000..3931010a --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/README.md @@ -0,0 +1,167 @@ +PHPASN1 +======= + +[![Build Status](https://secure.travis-ci.org/fgrosse/PHPASN1.png?branch=master)](http://travis-ci.org/fgrosse/PHPASN1) +[![PHP 7 ready](http://php7ready.timesplinter.ch/fgrosse/PHPASN1/badge.svg)](https://travis-ci.org/fgrosse/PHPASN1) +[![Coverage Status](https://coveralls.io/repos/fgrosse/PHPASN1/badge.svg?branch=master&service=github)](https://coveralls.io/github/fgrosse/PHPASN1?branch=master) + +[![Latest Stable Version](https://poser.pugx.org/fgrosse/phpasn1/v/stable.png)](https://packagist.org/packages/fgrosse/phpasn1) +[![Total Downloads](https://poser.pugx.org/fgrosse/phpasn1/downloads.png)](https://packagist.org/packages/fgrosse/phpasn1) +[![Latest Unstable Version](https://poser.pugx.org/fgrosse/phpasn1/v/unstable.png)](https://packagist.org/packages/fgrosse/phpasn1) +[![License](https://poser.pugx.org/fgrosse/phpasn1/license.png)](https://packagist.org/packages/fgrosse/phpasn1) + +A PHP Framework that allows you to encode and decode arbitrary [ASN.1][3] structures +using the [ITU-T X.690 Encoding Rules][4]. +This encoding is very frequently used in [X.509 PKI environments][5] or the communication between heterogeneous computer systems. + +The API allows you to encode ASN.1 structures to create binary data such as certificate +signing requests (CSR), X.509 certificates or certificate revocation lists (CRL). +PHPASN1 can also read [BER encoded][6] binary data into separate PHP objects that can be manipulated by the user and reencoded afterwards. + +The **changelog** can now be found at [CHANGELOG.md](CHANGELOG.md). + +## Dependencies + +PHPASN1 requires at least `PHP 7.0` and either the `gmp` or `bcmath` extension. +Support for older PHP versions (i.e. PHP 5.6) was dropped starting with `v2.0`. +If you must use an outdated PHP version consider using [PHPASN v1.5][13]. + +For the loading of object identifier names directly from the web [curl][7] is used. + +## Installation + +The preferred way to install this library is to rely on [Composer][2]: + +```bash +$ composer require fgrosse/phpasn1 +``` + +## Usage + +### Encoding ASN.1 Structures + +PHPASN1 offers you a class for each of the implemented ASN.1 universal types. +The constructors should be pretty self explanatory so you should have no big trouble getting started. +All data will be encoded using [DER encoding][8] + +```php +use FG\ASN1\OID; +use FG\ASN1\Universal\Integer; +use FG\ASN1\Universal\Boolean; +use FG\ASN1\Universal\Enumerated; +use FG\ASN1\Universal\IA5String; +use FG\ASN1\Universal\ObjectIdentifier; +use FG\ASN1\Universal\PrintableString; +use FG\ASN1\Universal\Sequence; +use FG\ASN1\Universal\Set; +use FG\ASN1\Universal\NullObject; + +$integer = new Integer(123456); +$boolean = new Boolean(true); +$enum = new Enumerated(1); +$ia5String = new IA5String('Hello world'); + +$asnNull = new NullObject(); +$objectIdentifier1 = new ObjectIdentifier('1.2.250.1.16.9'); +$objectIdentifier2 = new ObjectIdentifier(OID::RSA_ENCRYPTION); +$printableString = new PrintableString('Foo bar'); + +$sequence = new Sequence($integer, $boolean, $enum, $ia5String); +$set = new Set($sequence, $asnNull, $objectIdentifier1, $objectIdentifier2, $printableString); + +$myBinary = $sequence->getBinary(); +$myBinary .= $set->getBinary(); + +echo base64_encode($myBinary); +``` + + +### Decoding binary data + +Decoding BER encoded binary data is just as easy as encoding it: + +```php +use FG\ASN1\ASNObject; + +$base64String = ... +$binaryData = base64_decode($base64String); +$asnObject = ASNObject::fromBinary($binaryData); + + +// do stuff +``` + +If you already know exactly how your expected data should look like you can use the `FG\ASN1\TemplateParser`: + +```php +use FG\ASN1\TemplateParser; + +// first define your template +$template = [ + Identifier::SEQUENCE => [ + Identifier::SET => [ + Identifier::OBJECT_IDENTIFIER, + Identifier::SEQUENCE => [ + Identifier::INTEGER, + Identifier::BITSTRING, + ] + ] + ] +]; + +// if your binary data is not matching the template you provided this will throw an `\Exception`: +$parser = new TemplateParser(); +$object = $parser->parseBinary($data, $template); + +// there is also a convenience function if you parse binary data from base64: +$object = $parser->parseBase64($data, $template); +``` + +You can use this function to make sure your data has exactly the format you are expecting. + +### Navigating decoded data + +All constructed classes (i.e. `Sequence` and `Set`) can be navigated by array access or using an iterator. +You can find examples +[here](https://github.com/fgrosse/PHPASN1/blob/f6442cadda9d36f3518c737e32f28300a588b777/tests/ASN1/Universal/SequenceTest.php#L148-148), +[here](https://github.com/fgrosse/PHPASN1/blob/f6442cadda9d36f3518c737e32f28300a588b777/tests/ASN1/Universal/SequenceTest.php#L121) and +[here](https://github.com/fgrosse/PHPASN1/blob/f6442cadda9d36f3518c737e32f28300a588b777/tests/ASN1/TemplateParserTest.php#L45). + + +### Give me more examples! + +To see some example usage of the API classes or some generated output check out the [examples](https://github.com/fgrosse/PHPASN1/tree/master/examples). + + +### How do I contribute? + +If you found an issue or have a question submit a github issue with detailed information. + +In case you already know what caused the issue and feel in the mood to fix it, your code contributions are always welcome. Just fork the repository, implement your changes and make sure that you covered everything with tests. +Afterwards submit a pull request via github and be a little patient :) I usually try to comment and/or merge as soon as possible. + +#### Mailing list + +New features or questions can be discussed in [this google group/mailing list][12]. + +### Thanks + +To [all contributors][1] so far! + +## License + +This library is distributed under the [MIT License](LICENSE). + +[1]: https://github.com/fgrosse/PHPASN1/graphs/contributors +[2]: https://getcomposer.org/ +[3]: http://www.itu.int/ITU-T/asn1/ +[4]: http://www.itu.int/ITU-T/recommendations/rec.aspx?rec=x.690 +[5]: http://en.wikipedia.org/wiki/X.509 +[6]: http://en.wikipedia.org/wiki/X.690#BER_encoding +[7]: http://php.net/manual/en/book.curl.php +[8]: http://en.wikipedia.org/wiki/X.690#DER_encoding +[9]: https://styleci.io +[10]: https://coveralls.io/github/fgrosse/PHPASN1 +[11]: https://github.com/fgrosse/PHPASN1/blob/master/tests/ASN1/TemplateParserTest.php#L16 +[12]: https://groups.google.com/d/forum/phpasn1 +[13]: https://packagist.org/packages/fgrosse/phpasn1#1.5.2 diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/composer.json b/lam/lib/3rdParty/composer/fgrosse/phpasn1/composer.json new file mode 100644 index 00000000..079e07e5 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/composer.json @@ -0,0 +1,47 @@ +{ + "name": "fgrosse/phpasn1", + "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.", + "type": "library", + "homepage": "https://github.com/FGrosse/PHPASN1", + "license": "MIT", + "authors": [ + { + "name": "Friedrich Große", + "email": "friedrich.grosse@gmail.com", + "homepage": "https://github.com/FGrosse", + "role": "Author" + }, + { + "name": "All contributors", + "homepage": "https://github.com/FGrosse/PHPASN1/contributors" + } + ], + "keywords": [ "x690", "x.690", "x.509", "x509", "asn1", "asn.1", "ber", "der", "binary", "encoding", "decoding" ], + + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.3", + "satooshi/php-coveralls": "~2.0" + }, + "suggest": { + "ext-gmp": "GMP is the preferred extension for big integer calculations", + "php-curl": "For loading OID information from the web if they have not bee defined statically" + }, + "autoload": { + "psr-4": { + "FG\\": "lib/" + } + }, + "autoload-dev": { + "psr-4": { + "FG\\Test\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/ASNObject.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/ASNObject.php new file mode 100644 index 00000000..3b7f1621 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/ASNObject.php @@ -0,0 +1,355 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1; + +use FG\ASN1\Exception\ParserException; +use FG\ASN1\Universal\BitString; +use FG\ASN1\Universal\Boolean; +use FG\ASN1\Universal\Enumerated; +use FG\ASN1\Universal\GeneralizedTime; +use FG\ASN1\Universal\Integer; +use FG\ASN1\Universal\NullObject; +use FG\ASN1\Universal\ObjectIdentifier; +use FG\ASN1\Universal\RelativeObjectIdentifier; +use FG\ASN1\Universal\OctetString; +use FG\ASN1\Universal\Sequence; +use FG\ASN1\Universal\Set; +use FG\ASN1\Universal\UTCTime; +use FG\ASN1\Universal\IA5String; +use FG\ASN1\Universal\PrintableString; +use FG\ASN1\Universal\NumericString; +use FG\ASN1\Universal\UTF8String; +use FG\ASN1\Universal\UniversalString; +use FG\ASN1\Universal\CharacterString; +use FG\ASN1\Universal\GeneralString; +use FG\ASN1\Universal\VisibleString; +use FG\ASN1\Universal\GraphicString; +use FG\ASN1\Universal\BMPString; +use FG\ASN1\Universal\T61String; +use FG\ASN1\Universal\ObjectDescriptor; +use FG\Utility\BigInteger; +use LogicException; + +/** + * Class ASNObject is the base class for all concrete ASN.1 objects. + */ +abstract class ASNObject implements Parsable +{ + private $contentLength; + private $nrOfLengthOctets; + + /** + * Must return the number of octets of the content part. + * + * @return int + */ + abstract protected function calculateContentLength(); + + /** + * Encode the object using DER encoding. + * + * @see http://en.wikipedia.org/wiki/X.690#DER_encoding + * + * @return string the binary representation of an objects value + */ + abstract protected function getEncodedValue(); + + /** + * Return the content of this object in a non encoded form. + * This can be used to print the value in human readable form. + * + * @return mixed + */ + abstract public function getContent(); + + /** + * Return the object type octet. + * This should use the class constants of Identifier. + * + * @see Identifier + * + * @return int + */ + abstract public function getType(); + + /** + * Returns all identifier octets. If an inheriting class models a tag with + * the long form identifier format, it MUST reimplement this method to + * return all octets of the identifier. + * + * @throws LogicException If the identifier format is long form + * + * @return string Identifier as a set of octets + */ + public function getIdentifier() + { + $firstOctet = $this->getType(); + + if (Identifier::isLongForm($firstOctet)) { + throw new LogicException(sprintf('Identifier of %s uses the long form and must therefor override "ASNObject::getIdentifier()".', get_class($this))); + } + + return chr($firstOctet); + } + + /** + * Encode this object using DER encoding. + * + * @return string the full binary representation of the complete object + */ + public function getBinary() + { + $result = $this->getIdentifier(); + $result .= $this->createLengthPart(); + $result .= $this->getEncodedValue(); + + return $result; + } + + private function createLengthPart() + { + $contentLength = $this->getContentLength(); + $nrOfLengthOctets = $this->getNumberOfLengthOctets($contentLength); + + if ($nrOfLengthOctets == 1) { + return chr($contentLength); + } else { + // the first length octet determines the number subsequent length octets + $lengthOctets = chr(0x80 | ($nrOfLengthOctets - 1)); + for ($shiftLength = 8 * ($nrOfLengthOctets - 2); $shiftLength >= 0; $shiftLength -= 8) { + $lengthOctets .= chr($contentLength >> $shiftLength); + } + + return $lengthOctets; + } + } + + protected function getNumberOfLengthOctets($contentLength = null) + { + if (!isset($this->nrOfLengthOctets)) { + if ($contentLength == null) { + $contentLength = $this->getContentLength(); + } + + $this->nrOfLengthOctets = 1; + if ($contentLength > 127) { + do { // long form + $this->nrOfLengthOctets++; + $contentLength = $contentLength >> 8; + } while ($contentLength > 0); + } + } + + return $this->nrOfLengthOctets; + } + + protected function getContentLength() + { + if (!isset($this->contentLength)) { + $this->contentLength = $this->calculateContentLength(); + } + + return $this->contentLength; + } + + protected function setContentLength($newContentLength) + { + $this->contentLength = $newContentLength; + $this->getNumberOfLengthOctets($newContentLength); + } + + /** + * Returns the length of the whole object (including the identifier and length octets). + */ + public function getObjectLength() + { + $nrOfIdentifierOctets = strlen($this->getIdentifier()); + $contentLength = $this->getContentLength(); + $nrOfLengthOctets = $this->getNumberOfLengthOctets($contentLength); + + return $nrOfIdentifierOctets + $nrOfLengthOctets + $contentLength; + } + + public function __toString() + { + return $this->getContent(); + } + + /** + * Returns the name of the ASN.1 Type of this object. + * + * @see Identifier::getName() + */ + public function getTypeName() + { + return Identifier::getName($this->getType()); + } + + /** + * @param string $binaryData + * @param int $offsetIndex + * + * @throws ParserException + * + * @return \FG\ASN1\ASNObject + */ + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + if (strlen($binaryData) <= $offsetIndex) { + throw new ParserException('Can not parse binary from data: Offset index larger than input size', $offsetIndex); + } + + $identifierOctet = ord($binaryData[$offsetIndex]); + if (Identifier::isContextSpecificClass($identifierOctet) && Identifier::isConstructed($identifierOctet)) { + return ExplicitlyTaggedObject::fromBinary($binaryData, $offsetIndex); + } + + switch ($identifierOctet) { + case Identifier::BITSTRING: + return BitString::fromBinary($binaryData, $offsetIndex); + case Identifier::BOOLEAN: + return Boolean::fromBinary($binaryData, $offsetIndex); + case Identifier::ENUMERATED: + return Enumerated::fromBinary($binaryData, $offsetIndex); + case Identifier::INTEGER: + return Integer::fromBinary($binaryData, $offsetIndex); + case Identifier::NULL: + return NullObject::fromBinary($binaryData, $offsetIndex); + case Identifier::OBJECT_IDENTIFIER: + return ObjectIdentifier::fromBinary($binaryData, $offsetIndex); + case Identifier::RELATIVE_OID: + return RelativeObjectIdentifier::fromBinary($binaryData, $offsetIndex); + case Identifier::OCTETSTRING: + return OctetString::fromBinary($binaryData, $offsetIndex); + case Identifier::SEQUENCE: + return Sequence::fromBinary($binaryData, $offsetIndex); + case Identifier::SET: + return Set::fromBinary($binaryData, $offsetIndex); + case Identifier::UTC_TIME: + return UTCTime::fromBinary($binaryData, $offsetIndex); + case Identifier::GENERALIZED_TIME: + return GeneralizedTime::fromBinary($binaryData, $offsetIndex); + case Identifier::IA5_STRING: + return IA5String::fromBinary($binaryData, $offsetIndex); + case Identifier::PRINTABLE_STRING: + return PrintableString::fromBinary($binaryData, $offsetIndex); + case Identifier::NUMERIC_STRING: + return NumericString::fromBinary($binaryData, $offsetIndex); + case Identifier::UTF8_STRING: + return UTF8String::fromBinary($binaryData, $offsetIndex); + case Identifier::UNIVERSAL_STRING: + return UniversalString::fromBinary($binaryData, $offsetIndex); + case Identifier::CHARACTER_STRING: + return CharacterString::fromBinary($binaryData, $offsetIndex); + case Identifier::GENERAL_STRING: + return GeneralString::fromBinary($binaryData, $offsetIndex); + case Identifier::VISIBLE_STRING: + return VisibleString::fromBinary($binaryData, $offsetIndex); + case Identifier::GRAPHIC_STRING: + return GraphicString::fromBinary($binaryData, $offsetIndex); + case Identifier::BMP_STRING: + return BMPString::fromBinary($binaryData, $offsetIndex); + case Identifier::T61_STRING: + return T61String::fromBinary($binaryData, $offsetIndex); + case Identifier::OBJECT_DESCRIPTOR: + return ObjectDescriptor::fromBinary($binaryData, $offsetIndex); + default: + // At this point the identifier may be >1 byte. + if (Identifier::isConstructed($identifierOctet)) { + return new UnknownConstructedObject($binaryData, $offsetIndex); + } else { + $identifier = self::parseBinaryIdentifier($binaryData, $offsetIndex); + $lengthOfUnknownObject = self::parseContentLength($binaryData, $offsetIndex); + $offsetIndex += $lengthOfUnknownObject; + + return new UnknownObject($identifier, $lengthOfUnknownObject); + } + } + } + + protected static function parseIdentifier($identifierOctet, $expectedIdentifier, $offsetForExceptionHandling) + { + if (is_string($identifierOctet) || is_numeric($identifierOctet) == false) { + $identifierOctet = ord($identifierOctet); + } + + if ($identifierOctet != $expectedIdentifier) { + $message = 'Can not create an '.Identifier::getName($expectedIdentifier).' from an '.Identifier::getName($identifierOctet); + throw new ParserException($message, $offsetForExceptionHandling); + } + } + + protected static function parseBinaryIdentifier($binaryData, &$offsetIndex) + { + if (strlen($binaryData) <= $offsetIndex) { + throw new ParserException('Can not parse identifier from data: Offset index larger than input size', $offsetIndex); + } + + $identifier = $binaryData[$offsetIndex++]; + + if (Identifier::isLongForm(ord($identifier)) == false) { + return $identifier; + } + + while (true) { + if (strlen($binaryData) <= $offsetIndex) { + throw new ParserException('Can not parse identifier (long form) from data: Offset index larger than input size', $offsetIndex); + } + $nextOctet = $binaryData[$offsetIndex++]; + $identifier .= $nextOctet; + + if ((ord($nextOctet) & 0x80) === 0) { + // the most significant bit is 0 to we have reached the end of the identifier + break; + } + } + + return $identifier; + } + + protected static function parseContentLength(&$binaryData, &$offsetIndex, $minimumLength = 0) + { + if (strlen($binaryData) <= $offsetIndex) { + throw new ParserException('Can not parse content length from data: Offset index larger than input size', $offsetIndex); + } + + $contentLength = ord($binaryData[$offsetIndex++]); + if (($contentLength & 0x80) != 0) { + // bit 8 is set -> this is the long form + $nrOfLengthOctets = $contentLength & 0x7F; + $contentLength = BigInteger::create(0x00); + for ($i = 0; $i < $nrOfLengthOctets; $i++) { + if (strlen($binaryData) <= $offsetIndex) { + throw new ParserException('Can not parse content length (long form) from data: Offset index larger than input size', $offsetIndex); + } + $contentLength = $contentLength->shiftLeft(8)->add(ord($binaryData[$offsetIndex++])); + } + + if ($contentLength->compare(PHP_INT_MAX) > 0) { + throw new ParserException("Can not parse content length from data: length > maximum integer", $offsetIndex); + } + + $contentLength = $contentLength->toInteger(); + } + + if ($contentLength < $minimumLength) { + throw new ParserException('A '.get_called_class()." should have a content length of at least {$minimumLength}. Extracted length was {$contentLength}", $offsetIndex); + } + + $lenDataRemaining = strlen($binaryData) - $offsetIndex; + + if ($lenDataRemaining < $contentLength) { + throw new ParserException("Content length {$contentLength} exceeds remaining data length {$lenDataRemaining}", $offsetIndex); + } + + return $contentLength; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/AbstractString.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/AbstractString.php new file mode 100644 index 00000000..7e0d7ddb --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/AbstractString.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1; + +use Exception; + +abstract class AbstractString extends ASNObject implements Parsable +{ + /** @var string */ + protected $value; + private $checkStringForIllegalChars = true; + private $allowedCharacters = []; + + /** + * The abstract base class for ASN.1 classes which represent some string of character. + * + * @param string $string + */ + public function __construct($string) + { + $this->value = $string; + } + + public function getContent() + { + return $this->value; + } + + protected function allowCharacter($character) + { + $this->allowedCharacters[] = $character; + } + + protected function allowCharacters(...$characters) + { + foreach ($characters as $character) { + $this->allowedCharacters[] = $character; + } + } + + protected function allowNumbers() + { + foreach (range('0', '9') as $char) { + $this->allowedCharacters[] = (string) $char; + } + } + + protected function allowAllLetters() + { + $this->allowSmallLetters(); + $this->allowCapitalLetters(); + } + + protected function allowSmallLetters() + { + foreach (range('a', 'z') as $char) { + $this->allowedCharacters[] = $char; + } + } + + protected function allowCapitalLetters() + { + foreach (range('A', 'Z') as $char) { + $this->allowedCharacters[] = $char; + } + } + + protected function allowSpaces() + { + $this->allowedCharacters[] = ' '; + } + + protected function allowAll() + { + $this->checkStringForIllegalChars = false; + } + + protected function calculateContentLength() + { + return strlen($this->value); + } + + protected function getEncodedValue() + { + if ($this->checkStringForIllegalChars) { + $this->checkString(); + } + + return $this->value; + } + + protected function checkString() + { + $stringLength = $this->getContentLength(); + for ($i = 0; $i < $stringLength; $i++) { + if (in_array($this->value[$i], $this->allowedCharacters) == false) { + $typeName = Identifier::getName($this->getType()); + throw new Exception("Could not create a {$typeName} from the character sequence '{$this->value}'."); + } + } + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + $parsedObject = new static(''); + + self::parseIdentifier($binaryData[$offsetIndex], $parsedObject->getType(), $offsetIndex++); + $contentLength = self::parseContentLength($binaryData, $offsetIndex); + $string = substr($binaryData, $offsetIndex, $contentLength); + $offsetIndex += $contentLength; + + $parsedObject->value = $string; + $parsedObject->setContentLength($contentLength); + return $parsedObject; + } + + public static function isValid($string) + { + $testObject = new static($string); + try { + $testObject->checkString(); + + return true; + } catch (Exception $exception) { + return false; + } + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/AbstractTime.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/AbstractTime.php new file mode 100644 index 00000000..9fad5051 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/AbstractTime.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1; + +use DateInterval; +use DateTime; +use DateTimeZone; +use Exception; + +abstract class AbstractTime extends ASNObject +{ + /** @var DateTime */ + protected $value; + + public function __construct($dateTime = null, $dateTimeZone = 'UTC') + { + if ($dateTime == null || is_string($dateTime)) { + $timeZone = new DateTimeZone($dateTimeZone); + $dateTimeObject = new DateTime($dateTime, $timeZone); + if ($dateTimeObject == false) { + $errorMessage = $this->getLastDateTimeErrors(); + $className = Identifier::getName($this->getType()); + throw new Exception(sprintf("Could not create %s from date time string '%s': %s", $className, $dateTime, $errorMessage)); + } + $dateTime = $dateTimeObject; + } elseif (!$dateTime instanceof DateTime) { + throw new Exception('Invalid first argument for some instance of AbstractTime constructor'); + } + + $this->value = $dateTime; + } + + public function getContent() + { + return $this->value; + } + + protected function getLastDateTimeErrors() + { + $messages = ''; + $lastErrors = DateTime::getLastErrors(); + foreach ($lastErrors['errors'] as $errorMessage) { + $messages .= "{$errorMessage}, "; + } + + return substr($messages, 0, -2); + } + + public function __toString() + { + return $this->value->format("Y-m-d\tH:i:s"); + } + + protected static function extractTimeZoneData(&$binaryData, &$offsetIndex, DateTime $dateTime) + { + $sign = $binaryData[$offsetIndex++]; + $timeOffsetHours = intval(substr($binaryData, $offsetIndex, 2)); + $timeOffsetMinutes = intval(substr($binaryData, $offsetIndex + 2, 2)); + $offsetIndex += 4; + + $interval = new DateInterval("PT{$timeOffsetHours}H{$timeOffsetMinutes}M"); + if ($sign == '+') { + $dateTime->sub($interval); + } else { + $dateTime->add($interval); + } + + return $dateTime; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Base128.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Base128.php new file mode 100644 index 00000000..119ee7b9 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Base128.php @@ -0,0 +1,63 @@ +modulus(0x80)->toInteger()); + + $value = $value->shiftRight(7); + while ($value->compare(0) > 0) { + $octets .= chr(0x80 | $value->modulus(0x80)->toInteger()); + $value = $value->shiftRight(7); + } + + return strrev($octets); + } + + /** + * @param string $octets + * + * @throws InvalidArgumentException if the given octets represent a malformed base-128 value or the decoded value would exceed the the maximum integer length + * + * @return int + */ + public static function decode($octets) + { + $bitsPerOctet = 7; + $value = BigInteger::create(0); + $i = 0; + + while (true) { + if (!isset($octets[$i])) { + throw new InvalidArgumentException(sprintf('Malformed base-128 encoded value (0x%s).', strtoupper(bin2hex($octets)) ?: '0')); + } + + $octet = ord($octets[$i++]); + + $l1 = $value->shiftLeft($bitsPerOctet); + $r1 = $octet & 0x7f; + $value = $l1->add($r1); + + if (0 === ($octet & 0x80)) { + break; + } + } + + return (string)$value; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Composite/AttributeTypeAndValue.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Composite/AttributeTypeAndValue.php new file mode 100644 index 00000000..3f4027c2 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Composite/AttributeTypeAndValue.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Composite; + +use FG\ASN1\ASNObject; +use FG\ASN1\Universal\Sequence; +use FG\ASN1\Universal\ObjectIdentifier; + +class AttributeTypeAndValue extends Sequence +{ + /** + * @param ObjectIdentifier|string $objIdentifier + * @param \FG\ASN1\ASNObject $value + */ + public function __construct($objIdentifier, ASNObject $value) + { + if ($objIdentifier instanceof ObjectIdentifier == false) { + $objIdentifier = new ObjectIdentifier($objIdentifier); + } + parent::__construct($objIdentifier, $value); + } + + public function __toString() + { + return $this->children[0].': '.$this->children[1]; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Composite/RDNString.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Composite/RDNString.php new file mode 100644 index 00000000..e95e7acd --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Composite/RDNString.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Composite; + +use FG\ASN1\Universal\PrintableString; +use FG\ASN1\Universal\IA5String; +use FG\ASN1\Universal\UTF8String; + +class RDNString extends RelativeDistinguishedName +{ + /** + * @param string|\FG\ASN1\Universal\ObjectIdentifier $objectIdentifierString + * @param string|\FG\ASN1\ASNObject $value + */ + public function __construct($objectIdentifierString, $value) + { + if (PrintableString::isValid($value)) { + $value = new PrintableString($value); + } else { + if (IA5String::isValid($value)) { + $value = new IA5String($value); + } else { + $value = new UTF8String($value); + } + } + + parent::__construct($objectIdentifierString, $value); + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Composite/RelativeDistinguishedName.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Composite/RelativeDistinguishedName.php new file mode 100644 index 00000000..4185f41a --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Composite/RelativeDistinguishedName.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Composite; + +use FG\ASN1\Exception\NotImplementedException; +use FG\ASN1\ASNObject; +use FG\ASN1\Universal\Set; + +class RelativeDistinguishedName extends Set +{ + /** + * @param string|\FG\ASN1\Universal\ObjectIdentifier $objIdentifierString + * @param \FG\ASN1\ASNObject $value + */ + public function __construct($objIdentifierString, ASNObject $value) + { + // TODO: This does only support one element in the RelativeDistinguishedName Set but it it is defined as follows: + // RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue + parent::__construct(new AttributeTypeAndValue($objIdentifierString, $value)); + } + + public function getContent() + { + /** @var \FG\ASN1\ASNObject $firstObject */ + $firstObject = $this->children[0]; + return $firstObject->__toString(); + } + + /** + * At the current version this code can not work since the implementation of Construct requires + * the class to support a constructor without arguments. + * + * @deprecated this function is not yet implemented! Feel free to submit a pull request on github + * @param string $binaryData + * @param int $offsetIndex + * @throws NotImplementedException + */ + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + throw new NotImplementedException(); + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Construct.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Construct.php new file mode 100644 index 00000000..58f96137 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Construct.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use FG\ASN1\Exception\ParserException; +use Iterator; + +abstract class Construct extends ASNObject implements Countable, ArrayAccess, Iterator, Parsable +{ + /** @var \FG\ASN1\ASNObject[] */ + protected $children; + private $iteratorPosition; + + /** + * @param \FG\ASN1\ASNObject[] $children the variadic type hint is commented due to https://github.com/facebook/hhvm/issues/4858 + */ + public function __construct(/* HH_FIXME[4858]: variadic + strict */ ...$children) + { + $this->children = $children; + $this->iteratorPosition = 0; + } + + public function getContent() + { + return $this->children; + } + + public function rewind() + { + $this->iteratorPosition = 0; + } + + public function current() + { + return $this->children[$this->iteratorPosition]; + } + + public function key() + { + return $this->iteratorPosition; + } + + public function next() + { + $this->iteratorPosition++; + } + + public function valid() + { + return isset($this->children[$this->iteratorPosition]); + } + + public function offsetExists($offset) + { + return array_key_exists($offset, $this->children); + } + + public function offsetGet($offset) + { + return $this->children[$offset]; + } + + public function offsetSet($offset, $value) + { + if ($offset === null) { + $offset = count($this->children); + } + + $this->children[$offset] = $value; + } + + public function offsetUnset($offset) + { + unset($this->children[$offset]); + } + + protected function calculateContentLength() + { + $length = 0; + foreach ($this->children as $component) { + $length += $component->getObjectLength(); + } + + return $length; + } + + protected function getEncodedValue() + { + $result = ''; + foreach ($this->children as $component) { + $result .= $component->getBinary(); + } + + return $result; + } + + public function addChild(ASNObject $child) + { + $this->children[] = $child; + } + + public function addChildren(array $children) + { + foreach ($children as $child) { + $this->addChild($child); + } + } + + public function __toString() + { + $nrOfChildren = $this->getNumberOfChildren(); + $childString = $nrOfChildren == 1 ? 'child' : 'children'; + + return "[{$nrOfChildren} {$childString}]"; + } + + public function getNumberOfChildren() + { + return count($this->children); + } + + /** + * @return \FG\ASN1\ASNObject[] + */ + public function getChildren() + { + return $this->children; + } + + /** + * @return \FG\ASN1\ASNObject + */ + public function getFirstChild() + { + return $this->children[0]; + } + + /** + * @param string $binaryData + * @param int $offsetIndex + * + * @throws Exception\ParserException + * + * @return Construct|static + */ + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + $parsedObject = new static(); + self::parseIdentifier($binaryData[$offsetIndex], $parsedObject->getType(), $offsetIndex++); + $contentLength = self::parseContentLength($binaryData, $offsetIndex); + $startIndex = $offsetIndex; + + $children = []; + $octetsToRead = $contentLength; + while ($octetsToRead > 0) { + $newChild = ASNObject::fromBinary($binaryData, $offsetIndex); + $octetsToRead -= $newChild->getObjectLength(); + $children[] = $newChild; + } + + if ($octetsToRead !== 0) { + throw new ParserException("Sequence length incorrect", $startIndex); + } + + $parsedObject->addChildren($children); + $parsedObject->setContentLength($contentLength); + + return $parsedObject; + } + + public function count($mode = COUNT_NORMAL) + { + return count($this->children, $mode); + } + + public function getIterator() + { + return new ArrayIterator($this->children); + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Exception/NotImplementedException.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Exception/NotImplementedException.php new file mode 100644 index 00000000..c9f8e82e --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Exception/NotImplementedException.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Exception; + +class NotImplementedException extends \Exception +{ +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Exception/ParserException.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Exception/ParserException.php new file mode 100644 index 00000000..4bda4e87 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Exception/ParserException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Exception; + +class ParserException extends \Exception +{ + private $errorMessage; + private $offset; + + public function __construct($errorMessage, $offset) + { + $this->errorMessage = $errorMessage; + $this->offset = $offset; + parent::__construct("ASN.1 Parser Exception at offset {$this->offset}: {$this->errorMessage}"); + } + + public function getOffset() + { + return $this->offset; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/ExplicitlyTaggedObject.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/ExplicitlyTaggedObject.php new file mode 100644 index 00000000..b947a959 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/ExplicitlyTaggedObject.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1; + +use FG\ASN1\Exception\ParserException; + +/** + * Class ExplicitlyTaggedObject decorate an inner object with an additional tag that gives information about + * its context specific meaning. + * + * Explanation taken from A Layman's Guide to a Subset of ASN.1, BER, and DER: + * >>> An RSA Laboratories Technical Note + * >>> Burton S. Kaliski Jr. + * >>> Revised November 1, 1993 + * + * [...] + * Explicitly tagged types are derived from other types by adding an outer tag to the underlying type. + * In effect, explicitly tagged types are structured types consisting of one component, the underlying type. + * Explicit tagging is denoted by the ASN.1 keywords [class number] EXPLICIT (see Section 5.2). + * [...] + * + * @see http://luca.ntop.org/Teaching/Appunti/asn1.html + */ +class ExplicitlyTaggedObject extends ASNObject +{ + /** @var \FG\ASN1\ASNObject[] */ + private $decoratedObjects; + private $tag; + + /** + * @param int $tag + * @param \FG\ASN1\ASNObject $objects,... + */ + public function __construct($tag, /* HH_FIXME[4858]: variadic + strict */ ...$objects) + { + $this->tag = $tag; + $this->decoratedObjects = $objects; + } + + protected function calculateContentLength() + { + $length = 0; + foreach ($this->decoratedObjects as $object) { + $length += $object->getObjectLength(); + } + + return $length; + } + + protected function getEncodedValue() + { + $encoded = ''; + foreach ($this->decoratedObjects as $object) { + $encoded .= $object->getBinary(); + } + + return $encoded; + } + + public function getContent() + { + return $this->decoratedObjects; + } + + public function __toString() + { + switch ($length = count($this->decoratedObjects)) { + case 0: + return "Context specific empty object with tag [{$this->tag}]"; + case 1: + $decoratedType = Identifier::getShortName($this->decoratedObjects[0]->getType()); + return "Context specific $decoratedType with tag [{$this->tag}]"; + default: + return "$length context specific objects with tag [{$this->tag}]"; + } + } + + public function getType() + { + return ord($this->getIdentifier()); + } + + public function getIdentifier() + { + $identifier = Identifier::create(Identifier::CLASS_CONTEXT_SPECIFIC, true, $this->tag); + + return is_int($identifier) ? chr($identifier) : $identifier; + } + + public function getTag() + { + return $this->tag; + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + $identifier = self::parseBinaryIdentifier($binaryData, $offsetIndex); + $firstIdentifierOctet = ord($identifier); + assert(Identifier::isContextSpecificClass($firstIdentifierOctet), 'identifier octet should indicate context specific class'); + assert(Identifier::isConstructed($firstIdentifierOctet), 'identifier octet should indicate constructed object'); + $tag = Identifier::getTagNumber($identifier); + + $totalContentLength = self::parseContentLength($binaryData, $offsetIndex); + $remainingContentLength = $totalContentLength; + + $offsetIndexOfDecoratedObject = $offsetIndex; + $decoratedObjects = []; + + while ($remainingContentLength > 0) { + $nextObject = ASNObject::fromBinary($binaryData, $offsetIndex); + $remainingContentLength -= $nextObject->getObjectLength(); + $decoratedObjects[] = $nextObject; + } + + if ($remainingContentLength != 0) { + throw new ParserException("Context-Specific explicitly tagged object [$tag] starting at offset $offsetIndexOfDecoratedObject specifies a length of $totalContentLength octets but $remainingContentLength remain after parsing the content", $offsetIndexOfDecoratedObject); + } + + $parsedObject = new self($tag, ...$decoratedObjects); + $parsedObject->setContentLength($totalContentLength); + return $parsedObject; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Identifier.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Identifier.php new file mode 100644 index 00000000..b21caa34 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Identifier.php @@ -0,0 +1,339 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1; + +use Exception; + +/** + * The Identifier encodes the ASN.1 tag (class and number) of the type of a data value. + * + * Every identifier whose number is in the range 0 to 30 has the following structure: + * + * Bits: 8 7 6 5 4 3 2 1 + * | Class | P/C | Tag number | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Bits 8 and 7 define the class of this type ( Universal, Application, Context-specific or Private). + * Bit 6 encoded whether this type is primitive or constructed + * The remaining bits 5 - 1 encode the tag number + */ +class Identifier +{ + const CLASS_UNIVERSAL = 0x00; + const CLASS_APPLICATION = 0x01; + const CLASS_CONTEXT_SPECIFIC = 0x02; + const CLASS_PRIVATE = 0x03; + + const EOC = 0x00; // unsupported for now + const BOOLEAN = 0x01; + const INTEGER = 0x02; + const BITSTRING = 0x03; + const OCTETSTRING = 0x04; + const NULL = 0x05; + const OBJECT_IDENTIFIER = 0x06; + const OBJECT_DESCRIPTOR = 0x07; + const EXTERNAL = 0x08; // unsupported for now + const REAL = 0x09; // unsupported for now + const ENUMERATED = 0x0A; + const EMBEDDED_PDV = 0x0B; // unsupported for now + const UTF8_STRING = 0x0C; + const RELATIVE_OID = 0x0D; + // value 0x0E and 0x0F are reserved for future use + + const SEQUENCE = 0x30; + const SET = 0x31; + const NUMERIC_STRING = 0x12; + const PRINTABLE_STRING = 0x13; + const T61_STRING = 0x14; // sometimes referred to as TeletextString + const VIDEOTEXT_STRING = 0x15; + const IA5_STRING = 0x16; + const UTC_TIME = 0x17; + const GENERALIZED_TIME = 0x18; + const GRAPHIC_STRING = 0x19; + const VISIBLE_STRING = 0x1A; + const GENERAL_STRING = 0x1B; + const UNIVERSAL_STRING = 0x1C; + const CHARACTER_STRING = 0x1D; // Unrestricted character type + const BMP_STRING = 0x1E; + + const LONG_FORM = 0x1F; + const IS_CONSTRUCTED = 0x20; + + /** + * Creates an identifier. Short form identifiers are returned as integers + * for BC, long form identifiers will be returned as a string of octets. + * + * @param int $class + * @param bool $isConstructed + * @param int $tagNumber + * + * @throws Exception if the given arguments are invalid + * + * @return int|string + */ + public static function create($class, $isConstructed, $tagNumber) + { + if (!is_numeric($class) || $class < self::CLASS_UNIVERSAL || $class > self::CLASS_PRIVATE) { + throw new Exception(sprintf('Invalid class %d given', $class)); + } + + if (!is_bool($isConstructed)) { + throw new Exception("\$isConstructed must be a boolean value ($isConstructed given)"); + } + + $tagNumber = self::makeNumeric($tagNumber); + if ($tagNumber < 0) { + throw new Exception(sprintf('Invalid $tagNumber %d given. You can only use positive integers.', $tagNumber)); + } + + if ($tagNumber < self::LONG_FORM) { + return ($class << 6) | ($isConstructed << 5) | $tagNumber; + } + + $firstOctet = ($class << 6) | ($isConstructed << 5) | self::LONG_FORM; + + // Tag numbers formatted in long form are base-128 encoded. See X.609#8.1.2.4 + return chr($firstOctet).Base128::encode($tagNumber); + } + + public static function isConstructed($identifierOctet) + { + return ($identifierOctet & self::IS_CONSTRUCTED) === self::IS_CONSTRUCTED; + } + + public static function isLongForm($identifierOctet) + { + return ($identifierOctet & self::LONG_FORM) === self::LONG_FORM; + } + + /** + * Return the name of the mapped ASN.1 type with a preceding "ASN.1 ". + * + * Example: ASN.1 Octet String + * + * @see Identifier::getShortName() + * + * @param int|string $identifier + * + * @return string + */ + public static function getName($identifier) + { + $identifierOctet = self::makeNumeric($identifier); + + $typeName = static::getShortName($identifier); + + if (($identifierOctet & self::LONG_FORM) < self::LONG_FORM) { + $typeName = "ASN.1 {$typeName}"; + } + + return $typeName; + } + + /** + * Return the short version of the type name. + * + * If the given identifier octet can be mapped to a known universal type this will + * return its name. Else Identifier::getClassDescription() is used to retrieve + * information about the identifier. + * + * @see Identifier::getName() + * @see Identifier::getClassDescription() + * + * @param int|string $identifier + * + * @return string + */ + public static function getShortName($identifier) + { + $identifierOctet = self::makeNumeric($identifier); + + switch ($identifierOctet) { + case self::EOC: + return 'End-of-contents octet'; + case self::BOOLEAN: + return 'Boolean'; + case self::INTEGER: + return 'Integer'; + case self::BITSTRING: + return 'Bit String'; + case self::OCTETSTRING: + return 'Octet String'; + case self::NULL: + return 'NULL'; + case self::OBJECT_IDENTIFIER: + return 'Object Identifier'; + case self::OBJECT_DESCRIPTOR: + return 'Object Descriptor'; + case self::EXTERNAL: + return 'External Type'; + case self::REAL: + return 'Real'; + case self::ENUMERATED: + return 'Enumerated'; + case self::EMBEDDED_PDV: + return 'Embedded PDV'; + case self::UTF8_STRING: + return 'UTF8 String'; + case self::RELATIVE_OID: + return 'Relative OID'; + case self::SEQUENCE: + return 'Sequence'; + case self::SET: + return 'Set'; + case self::NUMERIC_STRING: + return 'Numeric String'; + case self::PRINTABLE_STRING: + return 'Printable String'; + case self::T61_STRING: + return 'T61 String'; + case self::VIDEOTEXT_STRING: + return 'Videotext String'; + case self::IA5_STRING: + return 'IA5 String'; + case self::UTC_TIME: + return 'UTC Time'; + case self::GENERALIZED_TIME: + return 'Generalized Time'; + case self::GRAPHIC_STRING: + return 'Graphic String'; + case self::VISIBLE_STRING: + return 'Visible String'; + case self::GENERAL_STRING: + return 'General String'; + case self::UNIVERSAL_STRING: + return 'Universal String'; + case self::CHARACTER_STRING: + return 'Character String'; + case self::BMP_STRING: + return 'BMP String'; + + case 0x0E: + return 'RESERVED (0x0E)'; + case 0x0F: + return 'RESERVED (0x0F)'; + + case self::LONG_FORM: + default: + $classDescription = self::getClassDescription($identifier); + + if (is_int($identifier)) { + $identifier = chr($identifier); + } + + return "$classDescription (0x".strtoupper(bin2hex($identifier)).')'; + } + } + + /** + * Returns a textual description of the information encoded in a given identifier octet. + * + * The first three (most significant) bytes are evaluated to determine if this is a + * constructed or primitive type and if it is either universal, application, context-specific or + * private. + * + * Example: + * Constructed context-specific + * Primitive universal + * + * @param int|string $identifier + * + * @return string + */ + public static function getClassDescription($identifier) + { + $identifierOctet = self::makeNumeric($identifier); + + if (self::isConstructed($identifierOctet)) { + $classDescription = 'Constructed '; + } else { + $classDescription = 'Primitive '; + } + $classBits = $identifierOctet >> 6; + switch ($classBits) { + case self::CLASS_UNIVERSAL: + $classDescription .= 'universal'; + break; + case self::CLASS_APPLICATION: + $classDescription .= 'application'; + break; + case self::CLASS_CONTEXT_SPECIFIC: + $tagNumber = self::getTagNumber($identifier); + $classDescription = "[$tagNumber] Context-specific"; + break; + case self::CLASS_PRIVATE: + $classDescription .= 'private'; + break; + + default: + return "INVALID IDENTIFIER OCTET: {$identifierOctet}"; + } + + return $classDescription; + } + + /** + * @param int|string $identifier + * + * @return int + */ + public static function getTagNumber($identifier) + { + $firstOctet = self::makeNumeric($identifier); + $tagNumber = $firstOctet & self::LONG_FORM; + + if ($tagNumber < self::LONG_FORM) { + return $tagNumber; + } + + if (is_numeric($identifier)) { + $identifier = chr($identifier); + } + return Base128::decode(substr($identifier, 1)); + } + + public static function isUniversalClass($identifier) + { + $identifier = self::makeNumeric($identifier); + + return $identifier >> 6 == self::CLASS_UNIVERSAL; + } + + public static function isApplicationClass($identifier) + { + $identifier = self::makeNumeric($identifier); + + return $identifier >> 6 == self::CLASS_APPLICATION; + } + + public static function isContextSpecificClass($identifier) + { + $identifier = self::makeNumeric($identifier); + + return $identifier >> 6 == self::CLASS_CONTEXT_SPECIFIC; + } + + public static function isPrivateClass($identifier) + { + $identifier = self::makeNumeric($identifier); + + return $identifier >> 6 == self::CLASS_PRIVATE; + } + + private static function makeNumeric($identifierOctet) + { + if (!is_numeric($identifierOctet)) { + return ord($identifierOctet); + } else { + return $identifierOctet; + } + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/OID.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/OID.php new file mode 100644 index 00000000..61ab01e1 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/OID.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1; + +class OID +{ + const RSA_ENCRYPTION = '1.2.840.113549.1.1.1'; + const MD5_WITH_RSA_ENCRYPTION = '1.2.840.113549.1.1.4'; + const SHA1_WITH_RSA_SIGNATURE = '1.2.840.113549.1.1.5'; + const PKCS9_EMAIL = '1.2.840.113549.1.9.1'; + const PKCS9_UNSTRUCTURED_NAME = '1.2.840.113549.1.9.2'; + const PKCS9_CONTENT_TYPE = '1.2.840.113549.1.9.3'; + const PKCS9_MESSAGE_DIGEST = '1.2.840.113549.1.9.4'; + const PKCS9_SIGNING_TIME = '1.2.840.113549.1.9.5'; + const PKCS9_EXTENSION_REQUEST = '1.2.840.113549.1.9.14'; + + // certificate extension identifier + const CERT_EXT_SUBJECT_DIRECTORY_ATTR = '2.5.29.9'; + const CERT_EXT_SUBJECT_KEY_IDENTIFIER = '2.5.29.14'; + const CERT_EXT_KEY_USAGE = '2.5.29.15'; + const CERT_EXT_PRIVATE_KEY_USAGE_PERIOD = '2.5.29.16'; + const CERT_EXT_SUBJECT_ALT_NAME = '2.5.29.17'; + const CERT_EXT_ISSUER_ALT_NAME = '2.5.29.18'; + const CERT_EXT_BASIC_CONSTRAINTS = '2.5.29.19'; + const CERT_EXT_CRL_NUMBER = '2.5.29.20'; + const CERT_EXT_REASON_CODE = '2.5.29.21'; + const CERT_EXT_INVALIDITY_DATE = '2.5.29.24'; + const CERT_EXT_DELTA_CRL_INDICATOR = '2.5.29.27'; + const CERT_EXT_ISSUING_DIST_POINT = '2.5.29.28'; + const CERT_EXT_CERT_ISSUER = '2.5.29.29'; + const CERT_EXT_NAME_CONSTRAINTS = '2.5.29.30'; + const CERT_EXT_CRL_DISTRIBUTION_POINTS = '2.5.29.31'; + const CERT_EXT_CERT_POLICIES = '2.5.29.32'; + const CERT_EXT_AUTHORITY_KEY_IDENTIFIER = '2.5.29.35'; + const CERT_EXT_EXTENDED_KEY_USAGE = '2.5.29.37'; + + // standard certificate files + const COMMON_NAME = '2.5.4.3'; + const SURNAME = '2.5.4.4'; + const SERIAL_NUMBER = '2.5.4.5'; + const COUNTRY_NAME = '2.5.4.6'; + const LOCALITY_NAME = '2.5.4.7'; + const STATE_OR_PROVINCE_NAME = '2.5.4.8'; + const STREET_ADDRESS = '2.5.4.9'; + const ORGANIZATION_NAME = '2.5.4.10'; + const OU_NAME = '2.5.4.11'; + const TITLE = '2.5.4.12'; + const DESCRIPTION = '2.5.4.13'; + const POSTAL_ADDRESS = '2.5.4.16'; + const POSTAL_CODE = '2.5.4.17'; + const AUTHORITY_REVOCATION_LIST = '2.5.4.38'; + + const AUTHORITY_INFORMATION_ACCESS = '1.3.6.1.5.5.7.1.1'; + + /** + * Returns the name of the given object identifier. + * + * Some OIDs are saved as class constants in this class. + * If the wanted oidString is not among them, this method will + * query http://oid-info.com for the right name. + * This behavior can be suppressed by setting the second method parameter to false. + * + * @param string $oidString + * @param bool $loadFromWeb + * + * @see self::loadFromWeb($oidString) + * + * @return string + */ + public static function getName($oidString, $loadFromWeb = true) + { + switch ($oidString) { + case self::RSA_ENCRYPTION: + return 'RSA Encryption'; + case self::MD5_WITH_RSA_ENCRYPTION: + return 'MD5 with RSA Encryption'; + case self::SHA1_WITH_RSA_SIGNATURE: + return 'SHA-1 with RSA Signature'; + + case self::PKCS9_EMAIL: + return 'PKCS #9 Email Address'; + case self::PKCS9_UNSTRUCTURED_NAME: + return 'PKCS #9 Unstructured Name'; + case self::PKCS9_CONTENT_TYPE: + return 'PKCS #9 Content Type'; + case self::PKCS9_MESSAGE_DIGEST: + return 'PKCS #9 Message Digest'; + case self::PKCS9_SIGNING_TIME: + return 'PKCS #9 Signing Time'; + + case self::COMMON_NAME: + return 'Common Name'; + case self::SURNAME: + return 'Surname'; + case self::SERIAL_NUMBER: + return 'Serial Number'; + case self::COUNTRY_NAME: + return 'Country Name'; + case self::LOCALITY_NAME: + return 'Locality Name'; + case self::STATE_OR_PROVINCE_NAME: + return 'State or Province Name'; + case self::STREET_ADDRESS: + return 'Street Address'; + case self::ORGANIZATION_NAME: + return 'Organization Name'; + case self::OU_NAME: + return 'Organization Unit Name'; + case self::TITLE: + return 'Title'; + case self::DESCRIPTION: + return 'Description'; + case self::POSTAL_ADDRESS: + return 'Postal Address'; + case self::POSTAL_CODE: + return 'Postal Code'; + case self::AUTHORITY_REVOCATION_LIST: + return 'Authority Revocation List'; + + case self::CERT_EXT_SUBJECT_DIRECTORY_ATTR: + return 'Subject directory attributes'; + case self::CERT_EXT_SUBJECT_KEY_IDENTIFIER: + return 'Subject key identifier'; + case self::CERT_EXT_KEY_USAGE: + return 'Key usage certificate extension'; + case self::CERT_EXT_PRIVATE_KEY_USAGE_PERIOD: + return 'Private key usage'; + case self::CERT_EXT_SUBJECT_ALT_NAME: + return 'Subject alternative name (SAN)'; + case self::CERT_EXT_ISSUER_ALT_NAME: + return 'Issuer alternative name'; + case self::CERT_EXT_BASIC_CONSTRAINTS: + return 'Basic constraints'; + case self::CERT_EXT_CRL_NUMBER: + return 'CRL number'; + case self::CERT_EXT_REASON_CODE: + return 'Reason code'; + case self::CERT_EXT_INVALIDITY_DATE: + return 'Invalidity code'; + case self::CERT_EXT_DELTA_CRL_INDICATOR: + return 'Delta CRL indicator'; + case self::CERT_EXT_ISSUING_DIST_POINT: + return 'Issuing distribution point'; + case self::CERT_EXT_CERT_ISSUER: + return 'Certificate issuer'; + case self::CERT_EXT_NAME_CONSTRAINTS: + return 'Name constraints'; + case self::CERT_EXT_CRL_DISTRIBUTION_POINTS: + return 'CRL distribution points'; + case self::CERT_EXT_CERT_POLICIES: + return 'Certificate policies '; + case self::CERT_EXT_AUTHORITY_KEY_IDENTIFIER: + return 'Authority key identifier'; + case self::CERT_EXT_EXTENDED_KEY_USAGE: + return 'Extended key usage'; + case self::AUTHORITY_INFORMATION_ACCESS: + return 'Certificate Authority Information Access (AIA)'; + + default: + if ($loadFromWeb) { + return self::loadFromWeb($oidString); + } else { + return $oidString; + } + } + } + + public static function loadFromWeb($oidString) + { + $ch = curl_init("http://oid-info.com/get/{$oidString}"); + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HEADER, 0); + + $contents = curl_exec($ch); + curl_close($ch); + + // This pattern needs to be updated as soon as the website layout of oid-info.com changes + preg_match_all('#(.+)\(\d+\)#si', $contents, $oidName); + + if (empty($oidName[1])) { + return "{$oidString} (unknown)"; + } + + $oidName = ucfirst(strtolower(preg_replace('/([A-Z][a-z])/', ' $1', $oidName[1][0]))); + $oidName = str_replace('-', ' ', $oidName); + + return "{$oidName} ({$oidString})"; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Parsable.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Parsable.php new file mode 100644 index 00000000..fa66b558 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Parsable.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1; + +use FG\ASN1\Exception\ParserException; + +/** + * The Parsable interface describes classes that can be parsed from their binary DER representation. + */ +interface Parsable +{ + /** + * Parse an instance of this class from its binary DER encoded representation. + * + * @param string $binaryData + * @param int $offsetIndex the offset at which parsing of the $binaryData is started. This parameter ill be modified + * to contain the offset index of the next object after this object has been parsed + * + * @throws ParserException if the given binary data is either invalid or not currently supported + * + * @return static + */ + public static function fromBinary(&$binaryData, &$offsetIndex = null); +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/TemplateParser.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/TemplateParser.php new file mode 100644 index 00000000..90a40b03 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/TemplateParser.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1; + +use Exception; +use FG\ASN1\Exception\ParserException; +use FG\ASN1\Universal\Sequence; + +class TemplateParser +{ + /** + * @param string $data + * @param array $template + * @return \FG\ASN1\ASNObject|Sequence + * @throws ParserException if there was an issue parsing + */ + public function parseBase64($data, array $template) + { + // TODO test with invalid data + return $this->parseBinary(base64_decode($data), $template); + } + + /** + * @param string $binary + * @param array $template + * @return \FG\ASN1\ASNObject|Sequence + * @throws ParserException if there was an issue parsing + */ + public function parseBinary($binary, array $template) + { + $parsedObject = ASNObject::fromBinary($binary); + + foreach ($template as $key => $value) { + $this->validate($parsedObject, $key, $value); + } + + return $parsedObject; + } + + private function validate(ASNObject $object, $key, $value) + { + if (is_array($value)) { + $this->assertTypeId($key, $object); + + /* @var Construct $object */ + foreach ($value as $key => $child) { + $this->validate($object->current(), $key, $child); + $object->next(); + } + } else { + $this->assertTypeId($value, $object); + } + } + + private function assertTypeId($expectedTypeId, ASNObject $object) + { + $actualType = $object->getType(); + if ($expectedTypeId != $actualType) { + throw new Exception("Expected type ($expectedTypeId) does not match actual type ($actualType"); + } + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/BMPString.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/BMPString.php new file mode 100644 index 00000000..83ec6a91 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/BMPString.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\AbstractString; +use FG\ASN1\Identifier; + +class BMPString extends AbstractString +{ + /** + * Creates a new ASN.1 BMP String. + * + * BMPString is a subtype of UniversalString that has its own + * unique tag and contains only the characters in the + * Basic Multilingual Plane (those corresponding to the first + * 64K-2 cells, less cells whose encoding is used to address + * characters outside the Basic Multilingual Plane) of ISO/IEC 10646-1. + * + * TODO The encodable characters of this type are not yet checked. + * + * @param string $string + */ + public function __construct($string) + { + $this->value = $string; + $this->allowAll(); + } + + public function getType() + { + return Identifier::BMP_STRING; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/BitString.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/BitString.php new file mode 100644 index 00000000..226695c5 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/BitString.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use Exception; +use FG\ASN1\Exception\ParserException; +use FG\ASN1\Parsable; +use FG\ASN1\Identifier; + +class BitString extends OctetString implements Parsable +{ + private $nrOfUnusedBits; + + /** + * Creates a new ASN.1 BitString object. + * + * @param string|int $value Either the hexadecimal value as a string (spaces are allowed - leading 0x is optional) or a numeric value + * @param int $nrOfUnusedBits the number of unused bits in the last octet [optional]. + * + * @throws Exception if the second parameter is no positive numeric value + */ + public function __construct($value, $nrOfUnusedBits = 0) + { + parent::__construct($value); + + if (!is_numeric($nrOfUnusedBits) || $nrOfUnusedBits < 0) { + throw new Exception('BitString: second parameter needs to be a positive number (or zero)!'); + } + + $this->nrOfUnusedBits = $nrOfUnusedBits; + } + + public function getType() + { + return Identifier::BITSTRING; + } + + protected function calculateContentLength() + { + // add one to the length for the first octet which encodes the number of unused bits in the last octet + return parent::calculateContentLength() + 1; + } + + protected function getEncodedValue() + { + // the first octet determines the number of unused bits + $nrOfUnusedBitsOctet = chr($this->nrOfUnusedBits); + $actualContent = parent::getEncodedValue(); + + return $nrOfUnusedBitsOctet.$actualContent; + } + + public function getNumberOfUnusedBits() + { + return $this->nrOfUnusedBits; + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + self::parseIdentifier($binaryData[$offsetIndex], Identifier::BITSTRING, $offsetIndex++); + $contentLength = self::parseContentLength($binaryData, $offsetIndex, 2); + + $nrOfUnusedBits = ord($binaryData[$offsetIndex]); + $value = substr($binaryData, $offsetIndex + 1, $contentLength - 1); + + if ($nrOfUnusedBits > 7 || // no less than 1 used, otherwise non-minimal + ($contentLength - 1) == 1 && $nrOfUnusedBits > 0 || // content length only 1, no + (ord($value[strlen($value)-1])&((1<<$nrOfUnusedBits)-1)) != 0 // unused bits set + ) { + throw new ParserException("Can not parse bit string with invalid padding", $offsetIndex); + } + + $offsetIndex += $contentLength; + + $parsedObject = new self(bin2hex($value), $nrOfUnusedBits); + $parsedObject->setContentLength($contentLength); + + return $parsedObject; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Boolean.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Boolean.php new file mode 100644 index 00000000..b73c99f1 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Boolean.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\ASNObject; +use FG\ASN1\Parsable; +use FG\ASN1\Identifier; +use FG\ASN1\Exception\ParserException; + +class Boolean extends ASNObject implements Parsable +{ + private $value; + + /** + * @param bool $value + */ + public function __construct($value) + { + $this->value = $value; + } + + public function getType() + { + return Identifier::BOOLEAN; + } + + protected function calculateContentLength() + { + return 1; + } + + protected function getEncodedValue() + { + if ($this->value == false) { + return chr(0x00); + } else { + return chr(0xFF); + } + } + + public function getContent() + { + if ($this->value == true) { + return 'TRUE'; + } else { + return 'FALSE'; + } + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + self::parseIdentifier($binaryData[$offsetIndex], Identifier::BOOLEAN, $offsetIndex++); + $contentLength = self::parseContentLength($binaryData, $offsetIndex); + + if ($contentLength != 1) { + throw new ParserException("An ASN.1 Boolean should not have a length other than one. Extracted length was {$contentLength}", $offsetIndex); + } + + $value = ord($binaryData[$offsetIndex++]); + $booleanValue = $value == 0xFF ? true : false; + + $parsedObject = new self($booleanValue); + $parsedObject->setContentLength($contentLength); + + return $parsedObject; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/CharacterString.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/CharacterString.php new file mode 100644 index 00000000..bfc170db --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/CharacterString.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\AbstractString; +use FG\ASN1\Identifier; + +class CharacterString extends AbstractString +{ + public function __construct($string) + { + $this->value = $string; + $this->allowAll(); + } + + public function getType() + { + return Identifier::CHARACTER_STRING; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Enumerated.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Enumerated.php new file mode 100644 index 00000000..06d04a3a --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Enumerated.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\Identifier; + +class Enumerated extends Integer +{ + public function getType() + { + return Identifier::ENUMERATED; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/GeneralString.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/GeneralString.php new file mode 100644 index 00000000..fb0346f0 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/GeneralString.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\AbstractString; +use FG\ASN1\Identifier; + +class GeneralString extends AbstractString +{ + /** + * Creates a new ASN.1 GeneralString. + * TODO The encodable characters of this type are not yet checked. + * + * @param string $string + */ + public function __construct($string) + { + $this->value = $string; + $this->allowAll(); + } + + public function getType() + { + return Identifier::GENERAL_STRING; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/GeneralizedTime.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/GeneralizedTime.php new file mode 100644 index 00000000..ca922097 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/GeneralizedTime.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\AbstractTime; +use FG\ASN1\Parsable; +use FG\ASN1\Identifier; +use FG\ASN1\Exception\ParserException; + +/** + * This ASN.1 universal type contains date and time information according to ISO 8601. + * + * The type consists of values representing: + * a) a calendar date, as defined in ISO 8601; and + * b) a time of day, to any of the precisions defined in ISO 8601, except for the hours value 24 which shall not be used; and + * c) the local time differential factor as defined in ISO 8601. + * + * Decoding of this type will accept the Basic Encoding Rules (BER) + * The encoding will comply with the Distinguished Encoding Rules (DER). + */ +class GeneralizedTime extends AbstractTime implements Parsable +{ + private $microseconds; + + public function __construct($dateTime = null, $dateTimeZone = 'UTC') + { + parent::__construct($dateTime, $dateTimeZone); + $this->microseconds = $this->value->format('u'); + if ($this->containsFractionalSecondsElement()) { + // DER requires us to remove trailing zeros + $this->microseconds = preg_replace('/([1-9]+)0+$/', '$1', $this->microseconds); + } + } + + public function getType() + { + return Identifier::GENERALIZED_TIME; + } + + protected function calculateContentLength() + { + $contentSize = 15; // YYYYMMDDHHmmSSZ + + if ($this->containsFractionalSecondsElement()) { + $contentSize += 1 + strlen($this->microseconds); + } + + return $contentSize; + } + + public function containsFractionalSecondsElement() + { + return intval($this->microseconds) > 0; + } + + protected function getEncodedValue() + { + $encodedContent = $this->value->format('YmdHis'); + if ($this->containsFractionalSecondsElement()) { + $encodedContent .= ".{$this->microseconds}"; + } + + return $encodedContent.'Z'; + } + + public function __toString() + { + if ($this->containsFractionalSecondsElement()) { + return $this->value->format("Y-m-d\tH:i:s.uP"); + } else { + return $this->value->format("Y-m-d\tH:i:sP"); + } + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + self::parseIdentifier($binaryData[$offsetIndex], Identifier::GENERALIZED_TIME, $offsetIndex++); + $lengthOfMinimumTimeString = 14; // YYYYMMDDHHmmSS + $contentLength = self::parseContentLength($binaryData, $offsetIndex, $lengthOfMinimumTimeString); + $maximumBytesToRead = $contentLength; + + $format = 'YmdGis'; + $content = substr($binaryData, $offsetIndex, $contentLength); + $dateTimeString = substr($content, 0, $lengthOfMinimumTimeString); + $offsetIndex += $lengthOfMinimumTimeString; + $maximumBytesToRead -= $lengthOfMinimumTimeString; + + if ($contentLength == $lengthOfMinimumTimeString) { + $localTimeZone = new \DateTimeZone(date_default_timezone_get()); + $dateTime = \DateTime::createFromFormat($format, $dateTimeString, $localTimeZone); + } else { + if ($binaryData[$offsetIndex] == '.') { + $maximumBytesToRead--; // account for the '.' + $nrOfFractionalSecondElements = 1; // account for the '.' + + while ($maximumBytesToRead > 0 + && $binaryData[$offsetIndex + $nrOfFractionalSecondElements] != '+' + && $binaryData[$offsetIndex + $nrOfFractionalSecondElements] != '-' + && $binaryData[$offsetIndex + $nrOfFractionalSecondElements] != 'Z') { + $nrOfFractionalSecondElements++; + $maximumBytesToRead--; + } + + $dateTimeString .= substr($binaryData, $offsetIndex, $nrOfFractionalSecondElements); + $offsetIndex += $nrOfFractionalSecondElements; + $format .= '.u'; + } + + $dateTime = \DateTime::createFromFormat($format, $dateTimeString, new \DateTimeZone('UTC')); + + if ($maximumBytesToRead > 0) { + if ($binaryData[$offsetIndex] == '+' + || $binaryData[$offsetIndex] == '-') { + $dateTime = static::extractTimeZoneData($binaryData, $offsetIndex, $dateTime); + } elseif ($binaryData[$offsetIndex++] != 'Z') { + throw new ParserException('Invalid ISO 8601 Time String', $offsetIndex); + } + } + } + + $parsedObject = new self($dateTime); + $parsedObject->setContentLength($contentLength); + + return $parsedObject; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/GraphicString.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/GraphicString.php new file mode 100644 index 00000000..4a01d67b --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/GraphicString.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\AbstractString; +use FG\ASN1\Identifier; + +class GraphicString extends AbstractString +{ + /** + * Creates a new ASN.1 Graphic String. + * TODO The encodable characters of this type are not yet checked. + * + * @param string $string + */ + public function __construct($string) + { + $this->value = $string; + $this->allowAll(); + } + + public function getType() + { + return Identifier::GRAPHIC_STRING; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/IA5String.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/IA5String.php new file mode 100644 index 00000000..33a80679 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/IA5String.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\AbstractString; +use FG\ASN1\Identifier; + +/** + * The International Alphabet No.5 (IA5) references the encoding of the ASCII characters. + * + * Each character in the data is encoded as 1 byte. + */ +class IA5String extends AbstractString +{ + public function __construct($string) + { + parent::__construct($string); + for ($i = 1; $i < 128; $i++) { + $this->allowCharacter(chr($i)); + } + } + + public function getType() + { + return Identifier::IA5_STRING; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Integer.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Integer.php new file mode 100644 index 00000000..fe3806ba --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Integer.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use Exception; +use FG\Utility\BigInteger; +use FG\ASN1\Exception\ParserException; +use FG\ASN1\ASNObject; +use FG\ASN1\Parsable; +use FG\ASN1\Identifier; + +class Integer extends ASNObject implements Parsable +{ + /** @var int */ + private $value; + + /** + * @param int $value + * + * @throws Exception if the value is not numeric + */ + public function __construct($value) + { + if (is_numeric($value) == false) { + throw new Exception("Invalid VALUE [{$value}] for ASN1_INTEGER"); + } + $this->value = $value; + } + + public function getType() + { + return Identifier::INTEGER; + } + + public function getContent() + { + return $this->value; + } + + protected function calculateContentLength() + { + return strlen($this->getEncodedValue()); + } + + protected function getEncodedValue() + { + $value = BigInteger::create($this->value, 10); + $negative = $value->compare(0) < 0; + if ($negative) { + $value = $value->absoluteValue(); + $limit = 0x80; + } else { + $limit = 0x7f; + } + + $mod = 0xff+1; + $values = []; + while($value->compare($limit) > 0) { + $values[] = $value->modulus($mod)->toInteger(); + $value = $value->shiftRight(8); + } + + $values[] = $value->modulus($mod)->toInteger(); + $numValues = count($values); + + if ($negative) { + for ($i = 0; $i < $numValues; $i++) { + $values[$i] = 0xff - $values[$i]; + } + for ($i = 0; $i < $numValues; $i++) { + $values[$i] += 1; + if ($values[$i] <= 0xff) { + break; + } + assert($i != $numValues - 1); + $values[$i] = 0; + } + if ($values[$numValues - 1] == 0x7f) { + $values[] = 0xff; + } + } + $values = array_reverse($values); + $r = pack("C*", ...$values); + return $r; + } + + private static function ensureMinimalEncoding($binaryData, $offsetIndex) + { + // All the first nine bits cannot equal 0 or 1, which would + // be non-minimal encoding for positive and negative integers respectively + if ((ord($binaryData[$offsetIndex]) == 0x00 && (ord($binaryData[$offsetIndex+1]) & 0x80) == 0) || + (ord($binaryData[$offsetIndex]) == 0xff && (ord($binaryData[$offsetIndex+1]) & 0x80) == 0x80)) { + throw new ParserException("Integer not minimally encoded", $offsetIndex); + } + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + $parsedObject = new static(0); + self::parseIdentifier($binaryData[$offsetIndex], $parsedObject->getType(), $offsetIndex++); + $contentLength = self::parseContentLength($binaryData, $offsetIndex, 1); + + if ($contentLength > 1) { + self::ensureMinimalEncoding($binaryData, $offsetIndex); + } + $isNegative = (ord($binaryData[$offsetIndex]) & 0x80) != 0x00; + $number = BigInteger::create(ord($binaryData[$offsetIndex++]) & 0x7F); + + for ($i = 0; $i < $contentLength - 1; $i++) { + $number = $number->multiply(0x100)->add(ord($binaryData[$offsetIndex++])); + } + + if ($isNegative) { + $number = $number->subtract(BigInteger::create(2)->toPower(8 * $contentLength - 1)); + } + + $parsedObject = new static((string)$number); + $parsedObject->setContentLength($contentLength); + + return $parsedObject; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/NullObject.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/NullObject.php new file mode 100644 index 00000000..b5293e4b --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/NullObject.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\ASNObject; +use FG\ASN1\Parsable; +use FG\ASN1\Identifier; +use FG\ASN1\Exception\ParserException; + +class NullObject extends ASNObject implements Parsable +{ + public function getType() + { + return Identifier::NULL; + } + + protected function calculateContentLength() + { + return 0; + } + + protected function getEncodedValue() + { + return null; + } + + public function getContent() + { + return 'NULL'; + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + self::parseIdentifier($binaryData[$offsetIndex], Identifier::NULL, $offsetIndex++); + $contentLength = self::parseContentLength($binaryData, $offsetIndex); + + if ($contentLength != 0) { + throw new ParserException("An ASN.1 Null should not have a length other than zero. Extracted length was {$contentLength}", $offsetIndex); + } + + $parsedObject = new self(); + $parsedObject->setContentLength(0); + + return $parsedObject; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/NumericString.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/NumericString.php new file mode 100644 index 00000000..13fb7c34 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/NumericString.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\AbstractString; +use FG\ASN1\Identifier; + +class NumericString extends AbstractString +{ + /** + * Creates a new ASN.1 NumericString. + * + * The following characters are permitted: + * Digits 0,1, ... 9 + * SPACE (space) + * + * @param string $string + */ + public function __construct($string) + { + $this->value = $string; + $this->allowNumbers(); + $this->allowSpaces(); + } + + public function getType() + { + return Identifier::NUMERIC_STRING; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/ObjectDescriptor.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/ObjectDescriptor.php new file mode 100644 index 00000000..1c5d3498 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/ObjectDescriptor.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\Identifier; + +class ObjectDescriptor extends GraphicString +{ + public function __construct($objectDescription) + { + parent::__construct($objectDescription); + } + + public function getType() + { + return Identifier::OBJECT_DESCRIPTOR; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/ObjectIdentifier.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/ObjectIdentifier.php new file mode 100644 index 00000000..150ce9c4 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/ObjectIdentifier.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use Exception; +use FG\ASN1\Base128; +use FG\ASN1\OID; +use FG\ASN1\ASNObject; +use FG\ASN1\Parsable; +use FG\ASN1\Identifier; +use FG\ASN1\Exception\ParserException; + +class ObjectIdentifier extends ASNObject implements Parsable +{ + protected $subIdentifiers; + protected $value; + + public function __construct($value) + { + $this->subIdentifiers = explode('.', $value); + $nrOfSubIdentifiers = count($this->subIdentifiers); + + for ($i = 0; $i < $nrOfSubIdentifiers; $i++) { + if (is_numeric($this->subIdentifiers[$i])) { + // enforce the integer type + $this->subIdentifiers[$i] = intval($this->subIdentifiers[$i]); + } else { + throw new Exception("[{$value}] is no valid object identifier (sub identifier ".($i + 1).' is not numeric)!'); + } + } + + // Merge the first to arcs of the OID registration tree (per ASN definition!) + if ($nrOfSubIdentifiers >= 2) { + $this->subIdentifiers[1] = ($this->subIdentifiers[0] * 40) + $this->subIdentifiers[1]; + unset($this->subIdentifiers[0]); + } + + $this->value = $value; + } + + public function getContent() + { + return $this->value; + } + + public function getType() + { + return Identifier::OBJECT_IDENTIFIER; + } + + protected function calculateContentLength() + { + $length = 0; + foreach ($this->subIdentifiers as $subIdentifier) { + do { + $subIdentifier = $subIdentifier >> 7; + $length++; + } while ($subIdentifier > 0); + } + + return $length; + } + + protected function getEncodedValue() + { + $encodedValue = ''; + foreach ($this->subIdentifiers as $subIdentifier) { + $encodedValue .= Base128::encode($subIdentifier); + } + + return $encodedValue; + } + + public function __toString() + { + return OID::getName($this->value); + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + self::parseIdentifier($binaryData[$offsetIndex], Identifier::OBJECT_IDENTIFIER, $offsetIndex++); + $contentLength = self::parseContentLength($binaryData, $offsetIndex, 1); + + $firstOctet = ord($binaryData[$offsetIndex++]); + $oidString = floor($firstOctet / 40).'.'.($firstOctet % 40); + $oidString .= '.'.self::parseOid($binaryData, $offsetIndex, $contentLength - 1); + + $parsedObject = new self($oidString); + $parsedObject->setContentLength($contentLength); + + return $parsedObject; + } + + /** + * Parses an object identifier except for the first octet, which is parsed + * differently. This way relative object identifiers can also be parsed + * using this. + * + * @param $binaryData + * @param $offsetIndex + * @param $octetsToRead + * + * @throws ParserException + * + * @return string + */ + protected static function parseOid(&$binaryData, &$offsetIndex, $octetsToRead) + { + $oid = ''; + + while ($octetsToRead > 0) { + $octets = ''; + + do { + if (0 === $octetsToRead) { + throw new ParserException('Malformed ASN.1 Object Identifier', $offsetIndex - 1); + } + + $octetsToRead--; + $octet = $binaryData[$offsetIndex++]; + $octets .= $octet; + } while (ord($octet) & 0x80); + + $oid .= sprintf('%d.', Base128::decode($octets)); + } + + // Remove trailing '.' + return substr($oid, 0, -1) ?: ''; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/OctetString.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/OctetString.php new file mode 100644 index 00000000..5d69ae7b --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/OctetString.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use Exception; +use FG\ASN1\ASNObject; +use FG\ASN1\Parsable; +use FG\ASN1\Identifier; + +class OctetString extends ASNObject implements Parsable +{ + protected $value; + + public function __construct($value) + { + if (is_string($value)) { + // remove gaps between hex digits + $value = preg_replace('/\s|0x/', '', $value); + } elseif (is_numeric($value)) { + $value = dechex($value); + } elseif ($value === null) { + return; + } else { + throw new Exception('OctetString: unrecognized input type!'); + } + + if (strlen($value) % 2 != 0) { + // transform values like 1F2 to 01F2 + $value = '0'.$value; + } + + $this->value = $value; + } + + public function getType() + { + return Identifier::OCTETSTRING; + } + + protected function calculateContentLength() + { + return strlen($this->value) / 2; + } + + protected function getEncodedValue() + { + $value = $this->value; + $result = ''; + + //Actual content + while (strlen($value) >= 2) { + // get the hex value byte by byte from the string and and add it to binary result + $result .= chr(hexdec(substr($value, 0, 2))); + $value = substr($value, 2); + } + + return $result; + } + + public function getContent() + { + return strtoupper($this->value); + } + + public function getBinaryContent() + { + return $this->getEncodedValue(); + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + self::parseIdentifier($binaryData[$offsetIndex], Identifier::OCTETSTRING, $offsetIndex++); + $contentLength = self::parseContentLength($binaryData, $offsetIndex); + + $value = substr($binaryData, $offsetIndex, $contentLength); + $offsetIndex += $contentLength; + + $parsedObject = new self(bin2hex($value)); + $parsedObject->setContentLength($contentLength); + + return $parsedObject; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/PrintableString.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/PrintableString.php new file mode 100644 index 00000000..fe6d4bc0 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/PrintableString.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\AbstractString; +use FG\ASN1\Identifier; + +class PrintableString extends AbstractString +{ + /** + * Creates a new ASN.1 PrintableString. + * + * The ITU-T X.680 Table 8 permits the following characters: + * Latin capital letters A,B, ... Z + * Latin small letters a,b, ... z + * Digits 0,1, ... 9 + * SPACE (space) + * APOSTROPHE ' + * LEFT PARENTHESIS ( + * RIGHT PARENTHESIS ) + * PLUS SIGN + + * COMMA , + * HYPHEN-MINUS - + * FULL STOP . + * SOLIDUS / + * COLON : + * EQUALS SIGN = + * QUESTION MARK ? + * + * @param string $string + */ + public function __construct($string) + { + $this->value = $string; + $this->allowNumbers(); + $this->allowAllLetters(); + $this->allowSpaces(); + $this->allowCharacters("'", '(', ')', '+', '-', '.', ',', '/', ':', '=', '?'); + } + + public function getType() + { + return Identifier::PRINTABLE_STRING; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/RelativeObjectIdentifier.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/RelativeObjectIdentifier.php new file mode 100644 index 00000000..2aa9643a --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/RelativeObjectIdentifier.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use Exception; +use FG\ASN1\Parsable; +use FG\ASN1\Identifier; +use FG\ASN1\Exception\ParserException; + +class RelativeObjectIdentifier extends ObjectIdentifier implements Parsable +{ + public function __construct($subIdentifiers) + { + $this->value = $subIdentifiers; + $this->subIdentifiers = explode('.', $subIdentifiers); + $nrOfSubIdentifiers = count($this->subIdentifiers); + + for ($i = 0; $i < $nrOfSubIdentifiers; $i++) { + if (is_numeric($this->subIdentifiers[$i])) { + // enforce the integer type + $this->subIdentifiers[$i] = intval($this->subIdentifiers[$i]); + } else { + throw new Exception("[{$subIdentifiers}] is no valid object identifier (sub identifier ".($i + 1).' is not numeric)!'); + } + } + } + + public function getType() + { + return Identifier::RELATIVE_OID; + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + self::parseIdentifier($binaryData[$offsetIndex], Identifier::RELATIVE_OID, $offsetIndex++); + $contentLength = self::parseContentLength($binaryData, $offsetIndex, 1); + + try { + $oidString = self::parseOid($binaryData, $offsetIndex, $contentLength); + } catch (ParserException $e) { + throw new ParserException('Malformed ASN.1 Relative Object Identifier', $e->getOffset()); + } + + $parsedObject = new self($oidString); + $parsedObject->setContentLength($contentLength); + + return $parsedObject; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Sequence.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Sequence.php new file mode 100644 index 00000000..0397cf12 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Sequence.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\Construct; +use FG\ASN1\Parsable; +use FG\ASN1\Identifier; + +class Sequence extends Construct implements Parsable +{ + public function getType() + { + return Identifier::SEQUENCE; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Set.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Set.php new file mode 100644 index 00000000..6e6d346f --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/Set.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\Identifier; + +class Set extends Sequence +{ + public function getType() + { + return Identifier::SET; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/T61String.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/T61String.php new file mode 100644 index 00000000..56418645 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/T61String.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\AbstractString; +use FG\ASN1\Identifier; + +class T61String extends AbstractString +{ + /** + * Creates a new ASN.1 T61 String. + * TODO The encodable characters of this type are not yet checked. + * + * @see http://en.wikipedia.org/wiki/ITU_T.61 + * + * @param string $string + */ + public function __construct($string) + { + $this->value = $string; + $this->allowAll(); + } + + public function getType() + { + return Identifier::T61_STRING; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/UTCTime.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/UTCTime.php new file mode 100644 index 00000000..c4d303cb --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/UTCTime.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\AbstractTime; +use FG\ASN1\Parsable; +use FG\ASN1\Identifier; +use FG\ASN1\Exception\ParserException; + +/** + * This ASN.1 universal type contains the calendar date and time. + * + * The precision is one minute or one second and optionally a + * local time differential from coordinated universal time. + * + * Decoding of this type will accept the Basic Encoding Rules (BER) + * The encoding will comply with the Distinguished Encoding Rules (DER). + */ +class UTCTime extends AbstractTime implements Parsable +{ + public function getType() + { + return Identifier::UTC_TIME; + } + + protected function calculateContentLength() + { + return 13; // Content is a string o the following format: YYMMDDhhmmssZ (13 octets) + } + + protected function getEncodedValue() + { + return $this->value->format('ymdHis').'Z'; + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + self::parseIdentifier($binaryData[$offsetIndex], Identifier::UTC_TIME, $offsetIndex++); + $contentLength = self::parseContentLength($binaryData, $offsetIndex, 11); + + $format = 'ymdGi'; + $dateTimeString = substr($binaryData, $offsetIndex, 10); + $offsetIndex += 10; + + // extract optional seconds part + if ($binaryData[$offsetIndex] != 'Z' + && $binaryData[$offsetIndex] != '+' + && $binaryData[$offsetIndex] != '-') { + $dateTimeString .= substr($binaryData, $offsetIndex, 2); + $offsetIndex += 2; + $format .= 's'; + } + + $dateTime = \DateTime::createFromFormat($format, $dateTimeString, new \DateTimeZone('UTC')); + + // extract time zone settings + if ($binaryData[$offsetIndex] == '+' + || $binaryData[$offsetIndex] == '-') { + $dateTime = static::extractTimeZoneData($binaryData, $offsetIndex, $dateTime); + } elseif ($binaryData[$offsetIndex++] != 'Z') { + throw new ParserException('Invalid UTC String', $offsetIndex); + } + + $parsedObject = new self($dateTime); + $parsedObject->setContentLength($contentLength); + + return $parsedObject; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/UTF8String.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/UTF8String.php new file mode 100644 index 00000000..cba568d3 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/UTF8String.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\AbstractString; +use FG\ASN1\Identifier; + +class UTF8String extends AbstractString +{ + /** + * Creates a new ASN.1 Universal String. + * TODO The encodable characters of this type are not yet checked. + * + * @param string $string + */ + public function __construct($string) + { + $this->value = $string; + $this->allowAll(); + } + + public function getType() + { + return Identifier::UTF8_STRING; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/UniversalString.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/UniversalString.php new file mode 100644 index 00000000..0c3fe1d0 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/UniversalString.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\AbstractString; +use FG\ASN1\Identifier; + +class UniversalString extends AbstractString +{ + /** + * Creates a new ASN.1 Universal String. + * TODO The encodable characters of this type are not yet checked. + * + * @see http://en.wikipedia.org/wiki/Universal_Character_Set + * + * @param string $string + */ + public function __construct($string) + { + $this->value = $string; + $this->allowAll(); + } + + public function getType() + { + return Identifier::UNIVERSAL_STRING; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/VisibleString.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/VisibleString.php new file mode 100644 index 00000000..d9326d3f --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/Universal/VisibleString.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +use FG\ASN1\AbstractString; +use FG\ASN1\Identifier; + +class VisibleString extends AbstractString +{ + /** + * Creates a new ASN.1 Visible String. + * TODO The encodable characters of this type are not yet checked. + * + * @param string $string + */ + public function __construct($string) + { + $this->value = $string; + $this->allowAll(); + } + + public function getType() + { + return Identifier::VISIBLE_STRING; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/UnknownConstructedObject.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/UnknownConstructedObject.php new file mode 100644 index 00000000..b19a07a1 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/UnknownConstructedObject.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1; + +class UnknownConstructedObject extends Construct +{ + private $identifier; + private $contentLength; + + /** + * @param string $binaryData + * @param int $offsetIndex + * + * @throws \FG\ASN1\Exception\ParserException + */ + public function __construct($binaryData, &$offsetIndex) + { + $this->identifier = self::parseBinaryIdentifier($binaryData, $offsetIndex); + $this->contentLength = self::parseContentLength($binaryData, $offsetIndex); + + $children = []; + $octetsToRead = $this->contentLength; + while ($octetsToRead > 0) { + $newChild = ASNObject::fromBinary($binaryData, $offsetIndex); + $octetsToRead -= $newChild->getObjectLength(); + $children[] = $newChild; + } + + parent::__construct(...$children); + } + + public function getType() + { + return ord($this->identifier); + } + + public function getIdentifier() + { + return $this->identifier; + } + + protected function calculateContentLength() + { + return $this->contentLength; + } + + protected function getEncodedValue() + { + return ''; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/UnknownObject.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/UnknownObject.php new file mode 100644 index 00000000..4ac536a9 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/ASN1/UnknownObject.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1; + +class UnknownObject extends ASNObject +{ + /** @var string */ + private $value; + + private $identifier; + + /** + * @param string|int $identifier Either the first identifier octet as int or all identifier bytes as a string + * @param int $contentLength + */ + public function __construct($identifier, $contentLength) + { + if (is_int($identifier)) { + $identifier = chr($identifier); + } + + $this->identifier = $identifier; + $this->value = "Unparsable Object ({$contentLength} bytes)"; + $this->setContentLength($contentLength); + } + + public function getContent() + { + return $this->value; + } + + public function getType() + { + return ord($this->identifier[0]); + } + + public function getIdentifier() + { + return $this->identifier; + } + + protected function calculateContentLength() + { + return $this->getContentLength(); + } + + protected function getEncodedValue() + { + return ''; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/Utility/BigInteger.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/Utility/BigInteger.php new file mode 100644 index 00000000..77ab5575 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/Utility/BigInteger.php @@ -0,0 +1,197 @@ +_fromInteger($val); + } + else { + // convert to string, if not already one + $val = (string)$val; + + // validate string + if (!preg_match('/^-?[0-9]+$/', $val)) { + throw new \InvalidArgumentException('Expects a string representation of an integer.'); + } + $ret->_fromString($val); + } + + return $ret; + } + + /** + * BigInteger constructor. + * Prevent directly instantiating object, use BigInteger::create instead. + */ + protected function __construct() + { + + } + + /** + * Subclasses must provide clone functionality. + * @return BigInteger + */ + abstract public function __clone(); + + /** + * Assign the instance value from base 10 string. + * @param string $str + */ + abstract protected function _fromString($str); + + /** + * Assign the instance value from an integer type. + * @param int $integer + */ + abstract protected function _fromInteger($integer); + + /** + * Must provide string implementation that returns base 10 number. + * @return string + */ + abstract public function __toString(); + + /* INFORMATIONAL FUNCTIONS */ + + /** + * Return integer, if possible. Throws an exception if the number can not be represented as a native integer. + * @return int + * @throws \OverflowException + */ + abstract public function toInteger(); + + /** + * Is represented integer negative? + * @return bool + */ + abstract public function isNegative(); + + /** + * Compare the integer with $number, returns a negative integer if $this is less than number, returns 0 if $this is + * equal to number and returns a positive integer if $this is greater than number. + * @param BigInteger|string|int $number + * @return int + */ + abstract public function compare($number); + + /* MODIFY */ + + /** + * Add another integer $b and returns the result. + * @param BigInteger|string|int $b + * @return BigInteger + */ + abstract public function add($b); + + /** + * Subtract $b from $this and returns the result. + * @param BigInteger|string|int $b + * @return BigInteger + */ + abstract public function subtract($b); + + /** + * Multiply value. + * @param BigInteger|string|int $b + * @return BigInteger + */ + abstract public function multiply($b); + + /** + * The value $this modulus $b. + * @param BigInteger|string|int $b + * @return BigInteger + */ + abstract public function modulus($b); + + /** + * Raise $this to the power of $b and returns the result. + * @param BigInteger|string|int $b + * @return BigInteger + */ + abstract public function toPower($b); + + /** + * Shift the value to the right by a set number of bits and returns the result. + * @param int $bits + * @return BigInteger + */ + abstract public function shiftRight($bits = 8); + + /** + * Shift the value to the left by a set number of bits and returns the result. + * @param int $bits + * @return BigInteger + */ + abstract public function shiftLeft($bits = 8); + + /** + * Returns the absolute value. + * @return BigInteger + */ + abstract public function absoluteValue(); +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/Utility/BigIntegerBcmath.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/Utility/BigIntegerBcmath.php new file mode 100644 index 00000000..25ad8916 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/Utility/BigIntegerBcmath.php @@ -0,0 +1,133 @@ +_str = (string)$str; + } + + protected function _fromInteger($integer) + { + $this->_str = (string)$integer; + } + + public function __toString() + { + return $this->_str; + } + + public function toInteger() + { + if ($this->compare(PHP_INT_MAX) > 0 || $this->compare(PHP_INT_MIN) < 0) { + throw new \OverflowException(sprintf('Can not represent %s as integer.', $this->_str)); + } + return (int)$this->_str; + } + + public function isNegative() + { + return bccomp($this->_str, '0', 0) < 0; + } + + protected function _unwrap($number) + { + if ($number instanceof self) { + return $number->_str; + } + return $number; + } + + public function compare($number) + { + return bccomp($this->_str, $this->_unwrap($number), 0); + } + + public function add($b) + { + $ret = new self(); + $ret->_str = bcadd($this->_str, $this->_unwrap($b), 0); + return $ret; + } + + public function subtract($b) + { + $ret = new self(); + $ret->_str = bcsub($this->_str, $this->_unwrap($b), 0); + return $ret; + } + + public function multiply($b) + { + $ret = new self(); + $ret->_str = bcmul($this->_str, $this->_unwrap($b), 0); + return $ret; + } + + public function modulus($b) + { + $ret = new self(); + if ($this->isNegative()) { + // bcmod handles negative numbers differently + $b = $this->_unwrap($b); + $ret->_str = bcsub($b, bcmod(bcsub('0', $this->_str, 0), $b), 0); + } + else { + $ret->_str = bcmod($this->_str, $this->_unwrap($b)); + } + return $ret; + } + + public function toPower($b) + { + $ret = new self(); + $ret->_str = bcpow($this->_str, $this->_unwrap($b), 0); + return $ret; + } + + public function shiftRight($bits = 8) + { + $ret = new self(); + $ret->_str = bcdiv($this->_str, bcpow('2', $bits)); + return $ret; + } + + public function shiftLeft($bits = 8) { + $ret = new self(); + $ret->_str = bcmul($this->_str, bcpow('2', $bits)); + return $ret; + } + + public function absoluteValue() + { + $ret = new self(); + if (-1 === bccomp($this->_str, '0', 0)) { + $ret->_str = bcsub('0', $this->_str, 0); + } + else { + $ret->_str = $this->_str; + } + return $ret; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/Utility/BigIntegerGmp.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/Utility/BigIntegerGmp.php new file mode 100644 index 00000000..0791226a --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/Utility/BigIntegerGmp.php @@ -0,0 +1,133 @@ +_rh = gmp_add($this->_rh, 0); + } + + protected function _fromString($str) + { + $this->_rh = gmp_init($str, 10); + } + + protected function _fromInteger($integer) + { + $this->_rh = gmp_init($integer, 10); + } + + public function __toString() + { + return gmp_strval($this->_rh, 10); + } + + public function toInteger() + { + if ($this->compare(PHP_INT_MAX) > 0 || $this->compare(PHP_INT_MIN) < 0) { + throw new \OverflowException(sprintf('Can not represent %s as integer.', $this)); + } + return gmp_intval($this->_rh); + } + + public function isNegative() + { + return gmp_sign($this->_rh) === -1; + } + + protected function _unwrap($number) + { + if ($number instanceof self) { + return $number->_rh; + } + return $number; + } + + public function compare($number) + { + return gmp_cmp($this->_rh, $this->_unwrap($number)); + } + + public function add($b) + { + $ret = new self(); + $ret->_rh = gmp_add($this->_rh, $this->_unwrap($b)); + return $ret; + } + + public function subtract($b) + { + $ret = new self(); + $ret->_rh = gmp_sub($this->_rh, $this->_unwrap($b)); + return $ret; + } + + public function multiply($b) + { + $ret = new self(); + $ret->_rh = gmp_mul($this->_rh, $this->_unwrap($b)); + return $ret; + } + + public function modulus($b) + { + $ret = new self(); + $ret->_rh = gmp_mod($this->_rh, $this->_unwrap($b)); + return $ret; + } + + public function toPower($b) + { + if ($b instanceof self) { + // gmp_pow accepts just an integer + if ($b->compare(PHP_INT_MAX) > 0) { + throw new \UnexpectedValueException('Unable to raise to power greater than PHP_INT_MAX.'); + } + $b = gmp_intval($b->_rh); + } + $ret = new self(); + $ret->_rh = gmp_pow($this->_rh, $b); + return $ret; + } + + public function shiftRight($bits=8) + { + $ret = new self(); + $ret->_rh = gmp_div($this->_rh, gmp_pow(2, $bits)); + return $ret; + } + + public function shiftLeft($bits=8) + { + $ret = new self(); + $ret->_rh = gmp_mul($this->_rh, gmp_pow(2, $bits)); + return $ret; + } + + public function absoluteValue() + { + $ret = new self(); + $ret->_rh = gmp_abs($this->_rh); + return $ret; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/AlgorithmIdentifier.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/AlgorithmIdentifier.php new file mode 100644 index 00000000..a06b56f7 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/AlgorithmIdentifier.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\X509; + +use FG\ASN1\Universal\NullObject; +use FG\ASN1\Composite\AttributeTypeAndValue; + +class AlgorithmIdentifier extends AttributeTypeAndValue +{ + public function __construct($objectIdentifierString) + { + parent::__construct($objectIdentifierString, new NullObject()); + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CSR/Attributes.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CSR/Attributes.php new file mode 100644 index 00000000..5a965e2f --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CSR/Attributes.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\X509\CSR; + +use FG\ASN1\ASNObject; +use FG\X509\CertificateExtensions; +use FG\ASN1\OID; +use FG\ASN1\Parsable; +use FG\ASN1\Construct; +use FG\ASN1\Identifier; +use FG\ASN1\Universal\Set; +use FG\ASN1\Universal\Sequence; +use FG\ASN1\Universal\ObjectIdentifier; + +class Attributes extends Construct implements Parsable +{ + public function getType() + { + return 0xA0; + } + + public function addAttribute($objectIdentifier, Set $attribute) + { + if (is_string($objectIdentifier)) { + $objectIdentifier = new ObjectIdentifier($objectIdentifier); + } + $attributeSequence = new Sequence($objectIdentifier, $attribute); + $attributeSequence->getNumberOfLengthOctets(); // length and number of length octets is calculated + $this->addChild($attributeSequence); + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + self::parseIdentifier($binaryData[$offsetIndex], 0xA0, $offsetIndex++); + $contentLength = self::parseContentLength($binaryData, $offsetIndex); + $octetsToRead = $contentLength; + + $parsedObject = new self(); + while ($octetsToRead > 0) { + $initialOffset = $offsetIndex; // used to calculate how much bits have been read + self::parseIdentifier($binaryData[$offsetIndex], Identifier::SEQUENCE, $offsetIndex++); + self::parseContentLength($binaryData, $offsetIndex); + + $objectIdentifier = ObjectIdentifier::fromBinary($binaryData, $offsetIndex); + $oidString = $objectIdentifier->getContent(); + if ($oidString == OID::PKCS9_EXTENSION_REQUEST) { + $attribute = CertificateExtensions::fromBinary($binaryData, $offsetIndex); + } else { + $attribute = ASNObject::fromBinary($binaryData, $offsetIndex); + } + + $parsedObject->addAttribute($objectIdentifier, $attribute); + $octetsToRead -= ($offsetIndex - $initialOffset); + } + + $parsedObject->setContentLength($contentLength); + + return $parsedObject; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CSR/CSR.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CSR/CSR.php new file mode 100644 index 00000000..69decdbb --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CSR/CSR.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\X509\CSR; + +use FG\ASN1\OID; +use FG\ASN1\Universal\Integer; +use FG\ASN1\Universal\BitString; +use FG\ASN1\Universal\Sequence; +use FG\X509\CertificateSubject; +use FG\X509\AlgorithmIdentifier; +use FG\X509\PublicKey; + +class CSR extends Sequence +{ + const CSR_VERSION_NR = 0; + + protected $subject; + protected $publicKey; + protected $signature; + protected $signatureAlgorithm; + + protected $startSequence; + + /** + * @param string $commonName + * @param string $email + * @param string $organization + * @param string $locality + * @param string $state + * @param string $country + * @param string $organizationalUnit + * @param string $publicKey + * @param string $signature + * @param string $signatureAlgorithm + */ + public function __construct($commonName, $email, $organization, $locality, $state, $country, $organizationalUnit, $publicKey, $signature, $signatureAlgorithm = OID::SHA1_WITH_RSA_SIGNATURE) + { + $this->subject = new CertificateSubject( + $commonName, + $email, + $organization, + $locality, + $state, + $country, + $organizationalUnit + ); + $this->publicKey = $publicKey; + $this->signature = $signature; + $this->signatureAlgorithm = $signatureAlgorithm; + + $this->createCSRSequence(); + } + + protected function createCSRSequence() + { + $versionNr = new Integer(self::CSR_VERSION_NR); + $publicKey = new PublicKey($this->publicKey); + $signature = new BitString($this->signature); + $signatureAlgorithm = new AlgorithmIdentifier($this->signatureAlgorithm); + + $certRequestInfo = new Sequence($versionNr, $this->subject, $publicKey); + + $this->addChild($certRequestInfo); + $this->addChild($signatureAlgorithm); + $this->addChild($signature); + } + + public function __toString() + { + $tmp = base64_encode($this->getBinary()); + + for ($i = 0; $i < strlen($tmp); $i++) { + if (($i + 2) % 65 == 0) { + $tmp = substr($tmp, 0, $i + 1)."\n".substr($tmp, $i + 1); + } + } + + $result = '-----BEGIN CERTIFICATE REQUEST-----'.PHP_EOL; + $result .= $tmp.PHP_EOL; + $result .= '-----END CERTIFICATE REQUEST-----'; + + return $result; + } + + public function getVersion() + { + return self::CSR_VERSION_NR; + } + + public function getOrganizationName() + { + return $this->subject->getOrganization(); + } + + public function getLocalName() + { + return $this->subject->getLocality(); + } + + public function getState() + { + return $this->subject->getState(); + } + + public function getCountry() + { + return $this->subject->getCountry(); + } + + public function getOrganizationalUnit() + { + return $this->subject->getOrganizationalUnit(); + } + + public function getPublicKey() + { + return $this->publicKey; + } + + public function getSignature() + { + return $this->signature; + } + + public function getSignatureAlgorithm() + { + return $this->signatureAlgorithm; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CertificateExtensions.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CertificateExtensions.php new file mode 100644 index 00000000..6ed1c6a7 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CertificateExtensions.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\X509; + +use FG\ASN1\Exception\ParserException; +use FG\ASN1\OID; +use FG\ASN1\ASNObject; +use FG\ASN1\Parsable; +use FG\ASN1\Identifier; +use FG\ASN1\Universal\OctetString; +use FG\ASN1\Universal\Set; +use FG\ASN1\Universal\Sequence; +use FG\ASN1\Universal\ObjectIdentifier; +use FG\X509\SAN\SubjectAlternativeNames; + +class CertificateExtensions extends Set implements Parsable +{ + private $innerSequence; + private $extensions = []; + + public function __construct() + { + $this->innerSequence = new Sequence(); + parent::__construct($this->innerSequence); + } + + public function addSubjectAlternativeNames(SubjectAlternativeNames $sans) + { + $this->addExtension(OID::CERT_EXT_SUBJECT_ALT_NAME, $sans); + } + + private function addExtension($oidString, ASNObject $extension) + { + $sequence = new Sequence(); + $sequence->addChild(new ObjectIdentifier($oidString)); + $sequence->addChild($extension); + + $this->innerSequence->addChild($sequence); + $this->extensions[] = $extension; + } + + public function getContent() + { + return $this->extensions; + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + self::parseIdentifier($binaryData[$offsetIndex], Identifier::SET, $offsetIndex++); + self::parseContentLength($binaryData, $offsetIndex); + + $tmpOffset = $offsetIndex; + $extensions = Sequence::fromBinary($binaryData, $offsetIndex); + $tmpOffset += 1 + $extensions->getNumberOfLengthOctets(); + + $parsedObject = new self(); + foreach ($extensions as $extension) { + if ($extension->getType() != Identifier::SEQUENCE) { + //FIXME wrong offset index + throw new ParserException('Could not parse Certificate Extensions: Expected ASN.1 Sequence but got '.$extension->getTypeName(), $offsetIndex); + } + + $tmpOffset += 1 + $extension->getNumberOfLengthOctets(); + $children = $extension->getChildren(); + if (count($children) < 2) { + throw new ParserException('Could not parse Certificate Extensions: Needs at least two child elements per extension sequence (object identifier and octet string)', $tmpOffset); + } + /** @var \FG\ASN1\ASNObject $objectIdentifier */ + $objectIdentifier = $children[0]; + + /** @var OctetString $octetString */ + $octetString = $children[1]; + + if ($objectIdentifier->getType() != Identifier::OBJECT_IDENTIFIER) { + throw new ParserException('Could not parse Certificate Extensions: Expected ASN.1 Object Identifier but got '.$extension->getTypeName(), $tmpOffset); + } + + $tmpOffset += $objectIdentifier->getObjectLength(); + + if ($objectIdentifier->getContent() == OID::CERT_EXT_SUBJECT_ALT_NAME) { + $sans = SubjectAlternativeNames::fromBinary($binaryData, $tmpOffset); + $parsedObject->addSubjectAlternativeNames($sans); + } else { + // can now only parse SANs. There might be more in the future + $tmpOffset += $octetString->getObjectLength(); + } + } + + $parsedObject->getBinary(); // Determine the number of content octets and object sizes once (just to let the equality unit tests pass :/ ) + return $parsedObject; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CertificateSubject.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CertificateSubject.php new file mode 100644 index 00000000..0a04d574 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/CertificateSubject.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\X509; + +use FG\ASN1\Composite\RelativeDistinguishedName; +use FG\ASN1\Identifier; +use FG\ASN1\OID; +use FG\ASN1\Parsable; +use FG\ASN1\Composite\RDNString; +use FG\ASN1\Universal\Sequence; + +class CertificateSubject extends Sequence implements Parsable +{ + private $commonName; + private $email; + private $organization; + private $locality; + private $state; + private $country; + private $organizationalUnit; + + /** + * @param string $commonName + * @param string $email + * @param string $organization + * @param string $locality + * @param string $state + * @param string $country + * @param string $organizationalUnit + */ + public function __construct($commonName, $email, $organization, $locality, $state, $country, $organizationalUnit) + { + parent::__construct( + new RDNString(OID::COUNTRY_NAME, $country), + new RDNString(OID::STATE_OR_PROVINCE_NAME, $state), + new RDNString(OID::LOCALITY_NAME, $locality), + new RDNString(OID::ORGANIZATION_NAME, $organization), + new RDNString(OID::OU_NAME, $organizationalUnit), + new RDNString(OID::COMMON_NAME, $commonName), + new RDNString(OID::PKCS9_EMAIL, $email) + ); + + $this->commonName = $commonName; + $this->email = $email; + $this->organization = $organization; + $this->locality = $locality; + $this->state = $state; + $this->country = $country; + $this->organizationalUnit = $organizationalUnit; + } + + public function getCommonName() + { + return $this->commonName; + } + + public function getEmail() + { + return $this->email; + } + + public function getOrganization() + { + return $this->organization; + } + + public function getLocality() + { + return $this->locality; + } + + public function getState() + { + return $this->state; + } + + public function getCountry() + { + return $this->country; + } + + public function getOrganizationalUnit() + { + return $this->organizationalUnit; + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + self::parseIdentifier($binaryData[$offsetIndex], Identifier::SEQUENCE, $offsetIndex++); + $contentLength = self::parseContentLength($binaryData, $offsetIndex); + + $names = []; + $octetsToRead = $contentLength; + while ($octetsToRead > 0) { + $relativeDistinguishedName = RelativeDistinguishedName::fromBinary($binaryData, $offsetIndex); + $octetsToRead -= $relativeDistinguishedName->getObjectLength(); + $names[] = $relativeDistinguishedName; + } + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/PrivateKey.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/PrivateKey.php new file mode 100644 index 00000000..d57ad865 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/PrivateKey.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\X509; + +use FG\ASN1\OID; +use FG\ASN1\Universal\NullObject; +use FG\ASN1\Universal\Sequence; +use FG\ASN1\Universal\BitString; +use FG\ASN1\Universal\ObjectIdentifier; + +class PrivateKey extends Sequence +{ + /** + * @param string $hexKey + * @param \FG\ASN1\ASNObject|string $algorithmIdentifierString + */ + public function __construct($hexKey, $algorithmIdentifierString = OID::RSA_ENCRYPTION) + { + parent::__construct( + new Sequence( + new ObjectIdentifier($algorithmIdentifierString), + new NullObject() + ), + new BitString($hexKey) + ); + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/PublicKey.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/PublicKey.php new file mode 100644 index 00000000..ab8b4514 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/PublicKey.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\X509; + +use FG\ASN1\OID; +use FG\ASN1\Universal\NullObject; +use FG\ASN1\Universal\Sequence; +use FG\ASN1\Universal\BitString; +use FG\ASN1\Universal\ObjectIdentifier; + +class PublicKey extends Sequence +{ + /** + * @param string $hexKey + * @param \FG\ASN1\ASNObject|string $algorithmIdentifierString + */ + public function __construct($hexKey, $algorithmIdentifierString = OID::RSA_ENCRYPTION) + { + parent::__construct( + new Sequence( + new ObjectIdentifier($algorithmIdentifierString), + new NullObject() + ), + new BitString($hexKey) + ); + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/SAN/DNSName.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/SAN/DNSName.php new file mode 100644 index 00000000..502738b0 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/SAN/DNSName.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\X509\SAN; + +use FG\ASN1\Universal\GeneralString; + +class DNSName extends GeneralString +{ + const IDENTIFIER = 0x82; // not sure yet why this is the identifier used in SAN extensions + + public function __construct($dnsNameString) + { + parent::__construct($dnsNameString); + } + + public function getType() + { + return self::IDENTIFIER; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/SAN/IPAddress.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/SAN/IPAddress.php new file mode 100644 index 00000000..f55be95b --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/SAN/IPAddress.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\X509\SAN; + +use FG\ASN1\ASNObject; +use FG\ASN1\Parsable; +use FG\ASN1\Exception\ParserException; + +class IPAddress extends ASNObject implements Parsable +{ + const IDENTIFIER = 0x87; // not sure yet why this is the identifier used in SAN extensions + + /** @var string */ + private $value; + + public function __construct($ipAddressString) + { + $this->value = $ipAddressString; + } + + public function getType() + { + return self::IDENTIFIER; + } + + public function getContent() + { + return $this->value; + } + + protected function calculateContentLength() + { + return 4; + } + + protected function getEncodedValue() + { + $ipParts = explode('.', $this->value); + $binary = chr($ipParts[0]); + $binary .= chr($ipParts[1]); + $binary .= chr($ipParts[2]); + $binary .= chr($ipParts[3]); + + return $binary; + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + self::parseIdentifier($binaryData[$offsetIndex], self::IDENTIFIER, $offsetIndex++); + $contentLength = self::parseContentLength($binaryData, $offsetIndex); + if ($contentLength != 4) { + throw new ParserException("A FG\\X509\SAN\IPAddress should have a content length of 4. Extracted length was {$contentLength}", $offsetIndex); + } + + $ipAddressString = ord($binaryData[$offsetIndex++]).'.'; + $ipAddressString .= ord($binaryData[$offsetIndex++]).'.'; + $ipAddressString .= ord($binaryData[$offsetIndex++]).'.'; + $ipAddressString .= ord($binaryData[$offsetIndex++]); + + $parsedObject = new self($ipAddressString); + $parsedObject->getObjectLength(); + + return $parsedObject; + } +} diff --git a/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/SAN/SubjectAlternativeNames.php b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/SAN/SubjectAlternativeNames.php new file mode 100644 index 00000000..271ddde7 --- /dev/null +++ b/lam/lib/3rdParty/composer/fgrosse/phpasn1/lib/X509/SAN/SubjectAlternativeNames.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\X509\SAN; + +use FG\ASN1\Exception\ParserException; +use FG\ASN1\ASNObject; +use FG\ASN1\OID; +use FG\ASN1\Parsable; +use FG\ASN1\Identifier; +use FG\ASN1\Universal\Sequence; + +/** + * See section 8.3.2.1 of ITU-T X.509. + */ +class SubjectAlternativeNames extends ASNObject implements Parsable +{ + private $alternativeNamesSequence; + + public function __construct() + { + $this->alternativeNamesSequence = new Sequence(); + } + + protected function calculateContentLength() + { + return $this->alternativeNamesSequence->getObjectLength(); + } + + public function getType() + { + return Identifier::OCTETSTRING; + } + + public function addDomainName(DNSName $domainName) + { + $this->alternativeNamesSequence->addChild($domainName); + } + + public function addIP(IPAddress $ip) + { + $this->alternativeNamesSequence->addChild($ip); + } + + public function getContent() + { + return $this->alternativeNamesSequence->getContent(); + } + + protected function getEncodedValue() + { + return $this->alternativeNamesSequence->getBinary(); + } + + public static function fromBinary(&$binaryData, &$offsetIndex = 0) + { + self::parseIdentifier($binaryData[$offsetIndex], Identifier::OCTETSTRING, $offsetIndex++); + $contentLength = self::parseContentLength($binaryData, $offsetIndex); + + if ($contentLength < 2) { + throw new ParserException('Can not parse Subject Alternative Names: The Sequence within the octet string after the Object identifier '.OID::CERT_EXT_SUBJECT_ALT_NAME." is too short ({$contentLength} octets)", $offsetIndex); + } + + $offsetOfSequence = $offsetIndex; + $sequence = Sequence::fromBinary($binaryData, $offsetIndex); + $offsetOfSequence += $sequence->getNumberOfLengthOctets() + 1; + + if ($sequence->getObjectLength() != $contentLength) { + throw new ParserException('Can not parse Subject Alternative Names: The Sequence length does not match the length of the surrounding octet string', $offsetIndex); + } + + $parsedObject = new self(); + /** @var \FG\ASN1\ASNObject $object */ + foreach ($sequence as $object) { + if ($object->getType() == DNSName::IDENTIFIER) { + $domainName = DNSName::fromBinary($binaryData, $offsetOfSequence); + $parsedObject->addDomainName($domainName); + } elseif ($object->getType() == IPAddress::IDENTIFIER) { + $ip = IPAddress::fromBinary($binaryData, $offsetOfSequence); + $parsedObject->addIP($ip); + } else { + throw new ParserException('Could not parse Subject Alternative Name: Only DNSName and IP SANs are currently supported', $offsetIndex); + } + } + + $parsedObject->getBinary(); // Determine the number of content octets and object sizes once (just to let the equality unit tests pass :/ ) + return $parsedObject; + } +} diff --git a/lam/lib/3rdParty/composer/nyholm/psr7/CHANGELOG.md b/lam/lib/3rdParty/composer/nyholm/psr7/CHANGELOG.md new file mode 100644 index 00000000..28f17afa --- /dev/null +++ b/lam/lib/3rdParty/composer/nyholm/psr7/CHANGELOG.md @@ -0,0 +1,85 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.2.1 + +### Changed + +- Added `.github` and `phpstan.neon.dist` to `.gitattributes`. + +## 1.2.0 + +### Changed + +- Change minimal port number to 0 (unix socket) +- Updated `Psr17Factory::createResponse` to respect the specification. If second + argument is not used, a standard reason phrase. If an empty string is passed, + then the reason phrase will be empty. + +### Fixed + +- Check for seekable on the stream resource. +- Fixed the `Response::$reason` should never be null. + +## 1.1.0 + +### Added + +- Improved performance +- More tests for `UploadedFile` and `HttplugFactory` + +### Removed + +- Dead code + +## 1.0.1 + +### Fixed + +- Handle `fopen` failing in createStreamFromFile according to PSR-7. +- Reduce execution path to speed up performance. +- Fixed typos. +- Code style. + +## 1.0.0 + +### Added + +- Support for final PSR-17 (HTTP factories). (`Psr17Factory`) +- Support for numeric header values. +- Support for empty header values. +- All classes are final +- `HttplugFactory` that implements factory interfaces from HTTPlug. + +### Changed + +- `ServerRequest` does not extend `Request`. + +### Removed + +- The HTTPlug discovery strategy was removed since it is included in php-http/discovery 1.4. +- `UploadedFileFactory()` was removed in favor for `Psr17Factory`. +- `ServerRequestFactory()` was removed in favor for `Psr17Factory`. +- `StreamFactory`, `UriFactory`, abd `MessageFactory`. Use `HttplugFactory` instead. +- `ServerRequestFactory::createServerRequestFromArray`, `ServerRequestFactory::createServerRequestFromArrays` and + `ServerRequestFactory::createServerRequestFromGlobals`. Please use the new `nyholm/psr7-server` instead. + +## 0.3.0 + +### Added + +- Return types. +- Many `InvalidArgumentException`s are thrown when you use invalid arguments. +- Integration tests for `UploadedFile` and `ServerRequest`. + +### Changed + +- We dropped PHP7.0 support. +- PSR-17 factories have been marked as internal. They do not fall under our BC promise until PSR-17 is accepted. +- `UploadedFileFactory::createUploadedFile` does not accept a string file path. + +## 0.2.3 + +No changelog before this release + diff --git a/lam/lib/3rdParty/composer/nyholm/psr7/LICENSE b/lam/lib/3rdParty/composer/nyholm/psr7/LICENSE new file mode 100644 index 00000000..d6c52312 --- /dev/null +++ b/lam/lib/3rdParty/composer/nyholm/psr7/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lam/lib/3rdParty/composer/nyholm/psr7/README.md b/lam/lib/3rdParty/composer/nyholm/psr7/README.md new file mode 100644 index 00000000..d626853b --- /dev/null +++ b/lam/lib/3rdParty/composer/nyholm/psr7/README.md @@ -0,0 +1,111 @@ +# PSR-7 implementation + +[![Latest Version](https://img.shields.io/github/release/Nyholm/psr7.svg?style=flat-square)](https://github.com/Nyholm/psr7/releases) +[![Build Status](https://img.shields.io/travis/Nyholm/psr7/master.svg?style=flat-square)](https://travis-ci.org/Nyholm/psr7) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/Nyholm/psr7.svg?style=flat-square)](https://scrutinizer-ci.com/g/Nyholm/psr7) +[![Quality Score](https://img.shields.io/scrutinizer/g/Nyholm/psr7.svg?style=flat-square)](https://scrutinizer-ci.com/g/Nyholm/psr7) +[![Total Downloads](https://poser.pugx.org/nyholm/psr7/downloads)](https://packagist.org/packages/nyholm/psr7) +[![Monthly Downloads](https://poser.pugx.org/nyholm/psr7/d/monthly.png)](https://packagist.org/packages/nyholm/psr7) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + + +A super lightweight PSR-7 implementation. Very strict and very fast. + +| Description | Guzzle | Zend | Slim | Nyholm | +| ---- | ------ | ---- | ---- | ------ | +| Lines of code | 3 000 | 3 000 | 1 700 | 1 000 | +| PHP7 | No | Yes | No | Yes | +| PSR-7* | 66% | 100% | 75% | 100% | +| PSR-17 | No | Yes | Yes | Yes | +| HTTPlug | No | No | No | Yes | +| Performance** | 1.34x | 1x | 1.16x | 1.75x | + +\* Percent of completed tests in https://github.com/php-http/psr7-integration-tests + +\** See benchmark at https://github.com/Nyholm/http-client-benchmark (higher is better) + +## Installation + +```bash +composer require nyholm/psr7 +``` + +If you are using Symfony Flex then you get all message factories registered as services. + +## Usage + +The PSR-7 objects do not contain any other public methods than those defined in +the [PSR-7 specification](https://www.php-fig.org/psr/psr-7/). + +### Create objects + +Use the PSR-17 factory to create requests, streams, URIs etc. + +```php +$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); +$request = $psr17Factory->createRequest('GET', 'http://tnyholm.se'); +$stream = $psr17Factory->createStream('foobar'); +``` + +### Sending a request + +With [HTTPlug](http://httplug.io/) or any other PSR-18 (HTTP client) you may send +requests like: + +```bash +composer require kriswallsmith/buzz +``` + +```php +$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); +$psr18Client = new \Buzz\Client\Curl($psr17Factory); + +$request = $psr17Factory->createRequest('GET', 'http://tnyholm.se'); +$response = $psr18Client->sendRequest($request); +``` + +### Create server requests + +The [`nyholm/psr7-server`](https://github.com/Nyholm/psr7-server) package can be used +to create server requests from PHP superglobals. + +```bash +composer require nyholm/psr7-server +``` + +```php +$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); + +$creator = new \Nyholm\Psr7Server\ServerRequestCreator( + $psr17Factory, // ServerRequestFactory + $psr17Factory, // UriFactory + $psr17Factory, // UploadedFileFactory + $psr17Factory // StreamFactory +); + +$serverRequest = $creator->fromGlobals(); +``` + +### Emitting a response + +```bash +composer require zendframework/zend-httphandlerrunner +``` + +```php +$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); + +$responseBody = $psr17Factory->createStream('Hello world'); +$response = $psr17Factory->createResponse(200)->withBody($responseBody); +(new \Zend\HttpHandlerRunner\Emitter\SapiEmitter())->emit($response); +``` + +## Our goal + +This package is currently maintained by [Tobias Nyholm](http://nyholm.se) and +[Martijn van der Ven](https://vanderven.se/martijn/). They have decided that the +goal of this library should be to provide a super strict implementation of +[PSR-7](https://www.php-fig.org/psr/psr-7/) that is blazing fast. + +The package will never include any extra features nor helper methods. All our classes +and functions exist because they are required to fulfill the PSR-7 specification. diff --git a/lam/lib/3rdParty/composer/nyholm/psr7/composer.json b/lam/lib/3rdParty/composer/nyholm/psr7/composer.json new file mode 100644 index 00000000..569cf14f --- /dev/null +++ b/lam/lib/3rdParty/composer/nyholm/psr7/composer.json @@ -0,0 +1,51 @@ +{ + "name": "nyholm/psr7", + "description": "A fast PHP7 implementation of PSR-7", + "license": "MIT", + "keywords": ["psr-7", "psr-17"], + "homepage": "http://tnyholm.se", + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "require": { + "php": "^7.1", + "psr/http-message": "^1.0", + "php-http/message-factory": "^1.0", + "psr/http-factory": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5", + "php-http/psr7-integration-tests": "dev-master", + "http-interop/http-factory-tests": "dev-master" + }, + "provide": { + "psr/http-message-implementation": "1.0", + "psr/http-factory-implementation": "1.0" + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\Nyholm\\Psr7\\": "tests/" + } + }, + "scripts": { + "test": "vendor/bin/phpunit", + "test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml" + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/nyholm/psr7/src/Factory/HttplugFactory.php b/lam/lib/3rdParty/composer/nyholm/psr7/src/Factory/HttplugFactory.php new file mode 100644 index 00000000..a2965414 --- /dev/null +++ b/lam/lib/3rdParty/composer/nyholm/psr7/src/Factory/HttplugFactory.php @@ -0,0 +1,40 @@ + + * @author Martijn van der Ven + */ +final class HttplugFactory implements MessageFactory, StreamFactory, UriFactory +{ + public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1') + { + return new Request($method, $uri, $headers, $body, $protocolVersion); + } + + public function createResponse($statusCode = 200, $reasonPhrase = null, array $headers = [], $body = null, $version = '1.1') + { + return new Response((int) $statusCode, $headers, $body, $version, $reasonPhrase); + } + + public function createStream($body = null) + { + return Stream::create($body ?? ''); + } + + public function createUri($uri = ''): UriInterface + { + if ($uri instanceof UriInterface) { + return $uri; + } + + return new Uri($uri); + } +} diff --git a/lam/lib/3rdParty/composer/nyholm/psr7/src/Factory/Psr17Factory.php b/lam/lib/3rdParty/composer/nyholm/psr7/src/Factory/Psr17Factory.php new file mode 100644 index 00000000..08caf857 --- /dev/null +++ b/lam/lib/3rdParty/composer/nyholm/psr7/src/Factory/Psr17Factory.php @@ -0,0 +1,73 @@ + + * @author Martijn van der Ven + */ +final class Psr17Factory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface +{ + public function createRequest(string $method, $uri): RequestInterface + { + return new Request($method, $uri); + } + + public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface + { + if (2 > \func_num_args()) { + // This will make the Response class to use a custom reasonPhrase + $reasonPhrase = null; + } + + return new Response($code, [], null, '1.1', $reasonPhrase); + } + + public function createStream(string $content = ''): StreamInterface + { + return Stream::create($content); + } + + public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface + { + $resource = @\fopen($filename, $mode); + if (false === $resource) { + if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'])) { + throw new \InvalidArgumentException('The mode ' . $mode . ' is invalid.'); + } + + throw new \RuntimeException('The file ' . $filename . ' cannot be opened.'); + } + + return Stream::create($resource); + } + + public function createStreamFromResource($resource): StreamInterface + { + return Stream::create($resource); + } + + public function createUploadedFile(StreamInterface $stream, int $size = null, int $error = \UPLOAD_ERR_OK, string $clientFilename = null, string $clientMediaType = null): UploadedFileInterface + { + if (null === $size) { + $size = $stream->getSize(); + } + + return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); + } + + public function createUri(string $uri = ''): UriInterface + { + return new Uri($uri); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return new ServerRequest($method, $uri, [], null, '1.1', $serverParams); + } +} diff --git a/lam/lib/3rdParty/composer/nyholm/psr7/src/MessageTrait.php b/lam/lib/3rdParty/composer/nyholm/psr7/src/MessageTrait.php new file mode 100644 index 00000000..d1e93cc1 --- /dev/null +++ b/lam/lib/3rdParty/composer/nyholm/psr7/src/MessageTrait.php @@ -0,0 +1,202 @@ + + * @author Martijn van der Ven + * + * @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise + */ +trait MessageTrait +{ + /** @var array Map of all registered headers, as original name => array of values */ + private $headers = []; + + /** @var array Map of lowercase header name => original name at registration */ + private $headerNames = []; + + /** @var string */ + private $protocol = '1.1'; + + /** @var StreamInterface|null */ + private $stream; + + public function getProtocolVersion(): string + { + return $this->protocol; + } + + public function withProtocolVersion($version): self + { + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = $version; + + return $new; + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function hasHeader($header): bool + { + return isset($this->headerNames[\strtolower($header)]); + } + + public function getHeader($header): array + { + $header = \strtolower($header); + if (!isset($this->headerNames[$header])) { + return []; + } + + $header = $this->headerNames[$header]; + + return $this->headers[$header]; + } + + public function getHeaderLine($header): string + { + return \implode(', ', $this->getHeader($header)); + } + + public function withHeader($header, $value): self + { + $value = $this->validateAndTrimHeader($header, $value); + $normalized = \strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + unset($new->headers[$new->headerNames[$normalized]]); + } + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + + return $new; + } + + public function withAddedHeader($header, $value): self + { + if (!\is_string($header) || '' === $header) { + throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string.'); + } + + $new = clone $this; + $new->setHeaders([$header => $value]); + + return $new; + } + + public function withoutHeader($header): self + { + $normalized = \strtolower($header); + if (!isset($this->headerNames[$normalized])) { + return $this; + } + + $header = $this->headerNames[$normalized]; + $new = clone $this; + unset($new->headers[$header], $new->headerNames[$normalized]); + + return $new; + } + + public function getBody(): StreamInterface + { + if (null === $this->stream) { + $this->stream = Stream::create(''); + } + + return $this->stream; + } + + public function withBody(StreamInterface $body): self + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + + return $new; + } + + private function setHeaders(array $headers): void + { + foreach ($headers as $header => $value) { + $value = $this->validateAndTrimHeader($header, $value); + $normalized = \strtolower($header); + if (isset($this->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $this->headers[$header] = \array_merge($this->headers[$header], $value); + } else { + $this->headerNames[$normalized] = $header; + $this->headers[$header] = $value; + } + } + } + + /** + * Make sure the header complies with RFC 7230. + * + * Header names must be a non-empty string consisting of token characters. + * + * Header values must be strings consisting of visible characters with all optional + * leading and trailing whitespace stripped. This method will always strip such + * optional whitespace. Note that the method does not allow folding whitespace within + * the values as this was deprecated for almost all instances by the RFC. + * + * header-field = field-name ":" OWS field-value OWS + * field-name = 1*( "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" + * / "_" / "`" / "|" / "~" / %x30-39 / ( %x41-5A / %x61-7A ) ) + * OWS = *( SP / HTAB ) + * field-value = *( ( %x21-7E / %x80-FF ) [ 1*( SP / HTAB ) ( %x21-7E / %x80-FF ) ] ) + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + */ + private function validateAndTrimHeader($header, $values): array + { + if (!\is_string($header) || 1 !== \preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@", $header)) { + throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string.'); + } + + if (!\is_array($values)) { + // This is simple, just one value. + if ((!\is_numeric($values) && !\is_string($values)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $values)) { + throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings.'); + } + + return [\trim((string) $values, " \t")]; + } + + if (empty($values)) { + throw new \InvalidArgumentException('Header values must be a string or an array of strings, empty array given.'); + } + + // Assert Non empty array + $returnValues = []; + foreach ($values as $v) { + if ((!\is_numeric($v) && !\is_string($v)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $v)) { + throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings.'); + } + + $returnValues[] = \trim((string) $v, " \t"); + } + + return $returnValues; + } +} diff --git a/lam/lib/3rdParty/composer/nyholm/psr7/src/Request.php b/lam/lib/3rdParty/composer/nyholm/psr7/src/Request.php new file mode 100644 index 00000000..84a9f2ab --- /dev/null +++ b/lam/lib/3rdParty/composer/nyholm/psr7/src/Request.php @@ -0,0 +1,45 @@ + + * @author Martijn van der Ven + */ +final class Request implements RequestInterface +{ + use MessageTrait; + use RequestTrait; + + /** + * @param string $method HTTP method + * @param string|UriInterface $uri URI + * @param array $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + */ + public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1') + { + if (!($uri instanceof UriInterface)) { + $uri = new Uri($uri); + } + + $this->method = $method; + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + + if (!$this->hasHeader('Host')) { + $this->updateHostFromUri(); + } + + // If we got no body, defer initialization of the stream until Request::getBody() + if ('' !== $body && null !== $body) { + $this->stream = Stream::create($body); + } + } +} diff --git a/lam/lib/3rdParty/composer/nyholm/psr7/src/RequestTrait.php b/lam/lib/3rdParty/composer/nyholm/psr7/src/RequestTrait.php new file mode 100644 index 00000000..f39993a1 --- /dev/null +++ b/lam/lib/3rdParty/composer/nyholm/psr7/src/RequestTrait.php @@ -0,0 +1,113 @@ + + * @author Martijn van der Ven + * + * @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise + */ +trait RequestTrait +{ + /** @var string */ + private $method; + + /** @var string|null */ + private $requestTarget; + + /** @var UriInterface|null */ + private $uri; + + public function getRequestTarget(): string + { + if (null !== $this->requestTarget) { + return $this->requestTarget; + } + + if ('' === $target = $this->uri->getPath()) { + $target = '/'; + } + if ('' !== $this->uri->getQuery()) { + $target .= '?' . $this->uri->getQuery(); + } + + return $target; + } + + public function withRequestTarget($requestTarget): self + { + if (\preg_match('#\s#', $requestTarget)) { + throw new \InvalidArgumentException('Invalid request target provided; cannot contain whitespace'); + } + + $new = clone $this; + $new->requestTarget = $requestTarget; + + return $new; + } + + public function getMethod(): string + { + return $this->method; + } + + public function withMethod($method): self + { + if (!\is_string($method)) { + throw new \InvalidArgumentException('Method must be a string'); + } + + $new = clone $this; + $new->method = $method; + + return $new; + } + + public function getUri(): UriInterface + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false): self + { + if ($uri === $this->uri) { + return $this; + } + + $new = clone $this; + $new->uri = $uri; + + if (!$preserveHost || !$this->hasHeader('Host')) { + $new->updateHostFromUri(); + } + + return $new; + } + + private function updateHostFromUri(): void + { + if ('' === $host = $this->uri->getHost()) { + return; + } + + if (null !== ($port = $this->uri->getPort())) { + $host .= ':' . $port; + } + + if (isset($this->headerNames['host'])) { + $header = $this->headerNames['host']; + } else { + $this->headerNames['host'] = $header = 'Host'; + } + + // Ensure Host is the first header. + // See: http://tools.ietf.org/html/rfc7230#section-5.4 + $this->headers = [$header => [$host]] + $this->headers; + } +} diff --git a/lam/lib/3rdParty/composer/nyholm/psr7/src/Response.php b/lam/lib/3rdParty/composer/nyholm/psr7/src/Response.php new file mode 100644 index 00000000..a75e93c3 --- /dev/null +++ b/lam/lib/3rdParty/composer/nyholm/psr7/src/Response.php @@ -0,0 +1,88 @@ + + * @author Martijn van der Ven + */ +final class Response implements ResponseInterface +{ + use MessageTrait; + + /** @var array Map of standard HTTP status code/reason phrases */ + private const PHRASES = [ + 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', + 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported', + 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect', + 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 511 => 'Network Authentication Required', + ]; + + /** @var string */ + private $reasonPhrase = ''; + + /** @var int */ + private $statusCode; + + /** + * @param int $status Status code + * @param array $headers Response headers + * @param string|resource|StreamInterface|null $body Response body + * @param string $version Protocol version + * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) + */ + public function __construct(int $status = 200, array $headers = [], $body = null, string $version = '1.1', string $reason = null) + { + // If we got no body, defer initialization of the stream until Response::getBody() + if ('' !== $body && null !== $body) { + $this->stream = Stream::create($body); + } + + $this->statusCode = $status; + $this->setHeaders($headers); + if (null === $reason && isset(self::PHRASES[$this->statusCode])) { + $this->reasonPhrase = self::PHRASES[$status]; + } else { + $this->reasonPhrase = $reason ?? ''; + } + + $this->protocol = $version; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function getReasonPhrase(): string + { + return $this->reasonPhrase; + } + + public function withStatus($code, $reasonPhrase = ''): self + { + if (!\is_int($code) && !\is_string($code)) { + throw new \InvalidArgumentException('Status code has to be an integer'); + } + + $code = (int) $code; + if ($code < 100 || $code > 599) { + throw new \InvalidArgumentException('Status code has to be an integer between 100 and 599'); + } + + $new = clone $this; + $new->statusCode = $code; + if ((null === $reasonPhrase || '' === $reasonPhrase) && isset(self::PHRASES[$new->statusCode])) { + $reasonPhrase = self::PHRASES[$new->statusCode]; + } + $new->reasonPhrase = $reasonPhrase; + + return $new; + } +} diff --git a/lam/lib/3rdParty/composer/nyholm/psr7/src/ServerRequest.php b/lam/lib/3rdParty/composer/nyholm/psr7/src/ServerRequest.php new file mode 100644 index 00000000..aff8721d --- /dev/null +++ b/lam/lib/3rdParty/composer/nyholm/psr7/src/ServerRequest.php @@ -0,0 +1,162 @@ + + * @author Martijn van der Ven + */ +final class ServerRequest implements ServerRequestInterface +{ + use MessageTrait; + use RequestTrait; + + /** @var array */ + private $attributes = []; + + /** @var array */ + private $cookieParams = []; + + /** @var array|object|null */ + private $parsedBody; + + /** @var array */ + private $queryParams = []; + + /** @var array */ + private $serverParams; + + /** @var UploadedFileInterface[] */ + private $uploadedFiles = []; + + /** + * @param string $method HTTP method + * @param string|UriInterface $uri URI + * @param array $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + * @param array $serverParams Typically the $_SERVER superglobal + */ + public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1', array $serverParams = []) + { + $this->serverParams = $serverParams; + + if (!($uri instanceof UriInterface)) { + $uri = new Uri($uri); + } + + $this->method = $method; + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + + if (!$this->hasHeader('Host')) { + $this->updateHostFromUri(); + } + + // If we got no body, defer initialization of the stream until ServerRequest::getBody() + if ('' !== $body && null !== $body) { + $this->stream = Stream::create($body); + } + } + + public function getServerParams(): array + { + return $this->serverParams; + } + + public function getUploadedFiles(): array + { + return $this->uploadedFiles; + } + + public function withUploadedFiles(array $uploadedFiles) + { + $new = clone $this; + $new->uploadedFiles = $uploadedFiles; + + return $new; + } + + public function getCookieParams(): array + { + return $this->cookieParams; + } + + public function withCookieParams(array $cookies) + { + $new = clone $this; + $new->cookieParams = $cookies; + + return $new; + } + + public function getQueryParams(): array + { + return $this->queryParams; + } + + public function withQueryParams(array $query) + { + $new = clone $this; + $new->queryParams = $query; + + return $new; + } + + public function getParsedBody() + { + return $this->parsedBody; + } + + public function withParsedBody($data) + { + if (!\is_array($data) && !\is_object($data) && null !== $data) { + throw new \InvalidArgumentException('First parameter to withParsedBody MUST be object, array or null'); + } + + $new = clone $this; + $new->parsedBody = $data; + + return $new; + } + + public function getAttributes(): array + { + return $this->attributes; + } + + public function getAttribute($attribute, $default = null) + { + if (false === \array_key_exists($attribute, $this->attributes)) { + return $default; + } + + return $this->attributes[$attribute]; + } + + public function withAttribute($attribute, $value): self + { + $new = clone $this; + $new->attributes[$attribute] = $value; + + return $new; + } + + public function withoutAttribute($attribute): self + { + if (false === \array_key_exists($attribute, $this->attributes)) { + return $this; + } + + $new = clone $this; + unset($new->attributes[$attribute]); + + return $new; + } +} diff --git a/lam/lib/3rdParty/composer/nyholm/psr7/src/Stream.php b/lam/lib/3rdParty/composer/nyholm/psr7/src/Stream.php new file mode 100644 index 00000000..a72ce0a6 --- /dev/null +++ b/lam/lib/3rdParty/composer/nyholm/psr7/src/Stream.php @@ -0,0 +1,257 @@ + + * @author Martijn van der Ven + */ +final class Stream implements StreamInterface +{ + /** @var resource|null A resource reference */ + private $stream; + + /** @var bool */ + private $seekable; + + /** @var bool */ + private $readable; + + /** @var bool */ + private $writable; + + /** @var array|mixed|void|null */ + private $uri; + + /** @var int|null */ + private $size; + + /** @var array Hash of readable and writable stream types */ + private const READ_WRITE_HASH = [ + 'read' => [ + 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, + 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, + 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a+' => true, + ], + 'write' => [ + 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, + 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, + 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true, + ], + ]; + + private function __construct() + { + } + + /** + * Creates a new PSR-7 stream. + * + * @param string|resource|StreamInterface $body + * + * @return StreamInterface + * + * @throws \InvalidArgumentException + */ + public static function create($body = ''): StreamInterface + { + if ($body instanceof StreamInterface) { + return $body; + } + + if (\is_string($body)) { + $resource = \fopen('php://temp', 'rw+'); + \fwrite($resource, $body); + $body = $resource; + } + + if (\is_resource($body)) { + $new = new self(); + $new->stream = $body; + $meta = \stream_get_meta_data($new->stream); + $new->seekable = $meta['seekable'] && 0 === \fseek($new->stream, 0, \SEEK_CUR); + $new->readable = isset(self::READ_WRITE_HASH['read'][$meta['mode']]); + $new->writable = isset(self::READ_WRITE_HASH['write'][$meta['mode']]); + $new->uri = $new->getMetadata('uri'); + + return $new; + } + + throw new \InvalidArgumentException('First argument to Stream::create() must be a string, resource or StreamInterface.'); + } + + /** + * Closes the stream when the destructed. + */ + public function __destruct() + { + $this->close(); + } + + public function __toString(): string + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + + return $this->getContents(); + } catch (\Exception $e) { + return ''; + } + } + + public function close(): void + { + if (isset($this->stream)) { + if (\is_resource($this->stream)) { + \fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (!isset($this->stream)) { + return null; + } + + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + public function getSize(): ?int + { + if (null !== $this->size) { + return $this->size; + } + + if (!isset($this->stream)) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($this->uri) { + \clearstatcache(true, $this->uri); + } + + $stats = \fstat($this->stream); + if (isset($stats['size'])) { + $this->size = $stats['size']; + + return $this->size; + } + + return null; + } + + public function tell(): int + { + if (false === $result = \ftell($this->stream)) { + throw new \RuntimeException('Unable to determine stream position'); + } + + return $result; + } + + public function eof(): bool + { + return !$this->stream || \feof($this->stream); + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function seek($offset, $whence = \SEEK_SET): void + { + if (!$this->seekable) { + throw new \RuntimeException('Stream is not seekable'); + } + + if (-1 === \fseek($this->stream, $offset, $whence)) { + throw new \RuntimeException('Unable to seek to stream position ' . $offset . ' with whence ' . \var_export($whence, true)); + } + } + + public function rewind(): void + { + $this->seek(0); + } + + public function isWritable(): bool + { + return $this->writable; + } + + public function write($string): int + { + if (!$this->writable) { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + + // We can't know the size after writing anything + $this->size = null; + + if (false === $result = \fwrite($this->stream, $string)) { + throw new \RuntimeException('Unable to write to stream'); + } + + return $result; + } + + public function isReadable(): bool + { + return $this->readable; + } + + public function read($length): string + { + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + + return \fread($this->stream, $length); + } + + public function getContents(): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Unable to read stream contents'); + } + + if (false === $contents = \stream_get_contents($this->stream)) { + throw new \RuntimeException('Unable to read stream contents'); + } + + return $contents; + } + + public function getMetadata($key = null) + { + if (!isset($this->stream)) { + return $key ? null : []; + } + + $meta = \stream_get_meta_data($this->stream); + + if (null === $key) { + return $meta; + } + + return $meta[$key] ?? null; + } +} diff --git a/lam/lib/3rdParty/composer/nyholm/psr7/src/UploadedFile.php b/lam/lib/3rdParty/composer/nyholm/psr7/src/UploadedFile.php new file mode 100644 index 00000000..757e70e9 --- /dev/null +++ b/lam/lib/3rdParty/composer/nyholm/psr7/src/UploadedFile.php @@ -0,0 +1,171 @@ + + * @author Martijn van der Ven + */ +final class UploadedFile implements UploadedFileInterface +{ + /** @var array */ + private const ERRORS = [ + \UPLOAD_ERR_OK => 1, + \UPLOAD_ERR_INI_SIZE => 1, + \UPLOAD_ERR_FORM_SIZE => 1, + \UPLOAD_ERR_PARTIAL => 1, + \UPLOAD_ERR_NO_FILE => 1, + \UPLOAD_ERR_NO_TMP_DIR => 1, + \UPLOAD_ERR_CANT_WRITE => 1, + \UPLOAD_ERR_EXTENSION => 1, + ]; + + /** @var string */ + private $clientFilename; + + /** @var string */ + private $clientMediaType; + + /** @var int */ + private $error; + + /** @var string|null */ + private $file; + + /** @var bool */ + private $moved = false; + + /** @var int */ + private $size; + + /** @var StreamInterface|null */ + private $stream; + + /** + * @param StreamInterface|string|resource $streamOrFile + * @param int $size + * @param int $errorStatus + * @param string|null $clientFilename + * @param string|null $clientMediaType + */ + public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null) + { + if (false === \is_int($errorStatus) || !isset(self::ERRORS[$errorStatus])) { + throw new \InvalidArgumentException('Upload file error status must be an integer value and one of the "UPLOAD_ERR_*" constants.'); + } + + if (false === \is_int($size)) { + throw new \InvalidArgumentException('Upload file size must be an integer'); + } + + if (null !== $clientFilename && !\is_string($clientFilename)) { + throw new \InvalidArgumentException('Upload file client filename must be a string or null'); + } + + if (null !== $clientMediaType && !\is_string($clientMediaType)) { + throw new \InvalidArgumentException('Upload file client media type must be a string or null'); + } + + $this->error = $errorStatus; + $this->size = $size; + $this->clientFilename = $clientFilename; + $this->clientMediaType = $clientMediaType; + + if (\UPLOAD_ERR_OK === $this->error) { + // Depending on the value set file or stream variable. + if (\is_string($streamOrFile)) { + $this->file = $streamOrFile; + } elseif (\is_resource($streamOrFile)) { + $this->stream = Stream::create($streamOrFile); + } elseif ($streamOrFile instanceof StreamInterface) { + $this->stream = $streamOrFile; + } else { + throw new \InvalidArgumentException('Invalid stream or file provided for UploadedFile'); + } + } + } + + /** + * @throws \RuntimeException if is moved or not ok + */ + private function validateActive(): void + { + if (\UPLOAD_ERR_OK !== $this->error) { + throw new \RuntimeException('Cannot retrieve stream due to upload error'); + } + + if ($this->moved) { + throw new \RuntimeException('Cannot retrieve stream after it has already been moved'); + } + } + + public function getStream(): StreamInterface + { + $this->validateActive(); + + if ($this->stream instanceof StreamInterface) { + return $this->stream; + } + + $resource = \fopen($this->file, 'r'); + + return Stream::create($resource); + } + + public function moveTo($targetPath): void + { + $this->validateActive(); + + if (!\is_string($targetPath) || '' === $targetPath) { + throw new \InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string'); + } + + if (null !== $this->file) { + $this->moved = 'cli' === \PHP_SAPI ? \rename($this->file, $targetPath) : \move_uploaded_file($this->file, $targetPath); + } else { + $stream = $this->getStream(); + if ($stream->isSeekable()) { + $stream->rewind(); + } + + // Copy the contents of a stream into another stream until end-of-file. + $dest = Stream::create(\fopen($targetPath, 'w')); + while (!$stream->eof()) { + if (!$dest->write($stream->read(1048576))) { + break; + } + } + + $this->moved = true; + } + + if (false === $this->moved) { + throw new \RuntimeException(\sprintf('Uploaded file could not be moved to %s', $targetPath)); + } + } + + public function getSize(): int + { + return $this->size; + } + + public function getError(): int + { + return $this->error; + } + + public function getClientFilename(): ?string + { + return $this->clientFilename; + } + + public function getClientMediaType(): ?string + { + return $this->clientMediaType; + } +} diff --git a/lam/lib/3rdParty/composer/nyholm/psr7/src/Uri.php b/lam/lib/3rdParty/composer/nyholm/psr7/src/Uri.php new file mode 100644 index 00000000..d67c0783 --- /dev/null +++ b/lam/lib/3rdParty/composer/nyholm/psr7/src/Uri.php @@ -0,0 +1,310 @@ + + * @author Martijn van der Ven + */ +final class Uri implements UriInterface +{ + private const SCHEMES = ['http' => 80, 'https' => 443]; + + private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; + + private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; + + /** @var string Uri scheme. */ + private $scheme = ''; + + /** @var string Uri user info. */ + private $userInfo = ''; + + /** @var string Uri host. */ + private $host = ''; + + /** @var int|null Uri port. */ + private $port; + + /** @var string Uri path. */ + private $path = ''; + + /** @var string Uri query string. */ + private $query = ''; + + /** @var string Uri fragment. */ + private $fragment = ''; + + public function __construct(string $uri = '') + { + if ('' !== $uri) { + if (false === $parts = \parse_url($uri)) { + throw new \InvalidArgumentException("Unable to parse URI: $uri"); + } + + // Apply parse_url parts to a URI. + $this->scheme = isset($parts['scheme']) ? \strtolower($parts['scheme']) : ''; + $this->userInfo = $parts['user'] ?? ''; + $this->host = isset($parts['host']) ? \strtolower($parts['host']) : ''; + $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null; + $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : ''; + $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : ''; + $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : ''; + if (isset($parts['pass'])) { + $this->userInfo .= ':' . $parts['pass']; + } + } + } + + public function __toString(): string + { + return self::createUriString($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment); + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getAuthority(): string + { + if ('' === $this->host) { + return ''; + } + + $authority = $this->host; + if ('' !== $this->userInfo) { + $authority = $this->userInfo . '@' . $authority; + } + + if (null !== $this->port) { + $authority .= ':' . $this->port; + } + + return $authority; + } + + public function getUserInfo(): string + { + return $this->userInfo; + } + + public function getHost(): string + { + return $this->host; + } + + public function getPort(): ?int + { + return $this->port; + } + + public function getPath(): string + { + return $this->path; + } + + public function getQuery(): string + { + return $this->query; + } + + public function getFragment(): string + { + return $this->fragment; + } + + public function withScheme($scheme): self + { + if (!\is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + + if ($this->scheme === $scheme = \strtolower($scheme)) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->port = $new->filterPort($new->port); + + return $new; + } + + public function withUserInfo($user, $password = null): self + { + $info = $user; + if (null !== $password && '' !== $password) { + $info .= ':' . $password; + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + + return $new; + } + + public function withHost($host): self + { + if (!\is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + + if ($this->host === $host = \strtolower($host)) { + return $this; + } + + $new = clone $this; + $new->host = $host; + + return $new; + } + + public function withPort($port): self + { + if ($this->port === $port = $this->filterPort($port)) { + return $this; + } + + $new = clone $this; + $new->port = $port; + + return $new; + } + + public function withPath($path): self + { + if ($this->path === $path = $this->filterPath($path)) { + return $this; + } + + $new = clone $this; + $new->path = $path; + + return $new; + } + + public function withQuery($query): self + { + if ($this->query === $query = $this->filterQueryAndFragment($query)) { + return $this; + } + + $new = clone $this; + $new->query = $query; + + return $new; + } + + public function withFragment($fragment): self + { + if ($this->fragment === $fragment = $this->filterQueryAndFragment($fragment)) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + + return $new; + } + + /** + * Create a URI string from its various parts. + */ + private static function createUriString(string $scheme, string $authority, string $path, string $query, string $fragment): string + { + $uri = ''; + if ('' !== $scheme) { + $uri .= $scheme . ':'; + } + + if ('' !== $authority) { + $uri .= '//' . $authority; + } + + if ('' !== $path) { + if ('/' !== $path[0]) { + if ('' !== $authority) { + // If the path is rootless and an authority is present, the path MUST be prefixed by "/" + $path = '/' . $path; + } + } elseif (isset($path[1]) && '/' === $path[1]) { + if ('' === $authority) { + // If the path is starting with more than one "/" and no authority is present, the + // starting slashes MUST be reduced to one. + $path = '/' . \ltrim($path, '/'); + } + } + + $uri .= $path; + } + + if ('' !== $query) { + $uri .= '?' . $query; + } + + if ('' !== $fragment) { + $uri .= '#' . $fragment; + } + + return $uri; + } + + /** + * Is a given port non-standard for the current scheme? + */ + private static function isNonStandardPort(string $scheme, int $port): bool + { + return !isset(self::SCHEMES[$scheme]) || $port !== self::SCHEMES[$scheme]; + } + + private function filterPort($port): ?int + { + if (null === $port) { + return null; + } + + $port = (int) $port; + if (0 > $port || 0xffff < $port) { + throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 0 and 65535', $port)); + } + + return self::isNonStandardPort($this->scheme, $port) ? $port : null; + } + + private function filterPath($path): string + { + if (!\is_string($path)) { + throw new \InvalidArgumentException('Path must be a string'); + } + + return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $path); + } + + private function filterQueryAndFragment($str): string + { + if (!\is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + + return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $str); + } + + private static function rawurlencodeMatchZero(array $match): string + { + return \rawurlencode($match[0]); + } +} diff --git a/lam/lib/3rdParty/composer/paragonie/random_compat/LICENSE b/lam/lib/3rdParty/composer/paragonie/random_compat/LICENSE new file mode 100644 index 00000000..45c7017d --- /dev/null +++ b/lam/lib/3rdParty/composer/paragonie/random_compat/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Paragon Initiative Enterprises + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/lam/lib/3rdParty/composer/paragonie/random_compat/build-phar.sh b/lam/lib/3rdParty/composer/paragonie/random_compat/build-phar.sh new file mode 100755 index 00000000..b4a5ba31 --- /dev/null +++ b/lam/lib/3rdParty/composer/paragonie/random_compat/build-phar.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) ) + +php -dphar.readonly=0 "$basedir/other/build_phar.php" $* \ No newline at end of file diff --git a/lam/lib/3rdParty/composer/paragonie/random_compat/composer.json b/lam/lib/3rdParty/composer/paragonie/random_compat/composer.json new file mode 100644 index 00000000..1fa8de9f --- /dev/null +++ b/lam/lib/3rdParty/composer/paragonie/random_compat/composer.json @@ -0,0 +1,34 @@ +{ + "name": "paragonie/random_compat", + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "random", + "polyfill", + "pseudorandom" + ], + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "support": { + "issues": "https://github.com/paragonie/random_compat/issues", + "email": "info@paragonie.com", + "source": "https://github.com/paragonie/random_compat" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "vimeo/psalm": "^1", + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + } +} diff --git a/lam/lib/3rdParty/composer/paragonie/random_compat/dist/random_compat.phar.pubkey b/lam/lib/3rdParty/composer/paragonie/random_compat/dist/random_compat.phar.pubkey new file mode 100644 index 00000000..eb50ebfc --- /dev/null +++ b/lam/lib/3rdParty/composer/paragonie/random_compat/dist/random_compat.phar.pubkey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm +pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p ++h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc +-----END PUBLIC KEY----- diff --git a/lam/lib/3rdParty/composer/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/lam/lib/3rdParty/composer/paragonie/random_compat/dist/random_compat.phar.pubkey.asc new file mode 100644 index 00000000..6a1d7f30 --- /dev/null +++ b/lam/lib/3rdParty/composer/paragonie/random_compat/dist/random_compat.phar.pubkey.asc @@ -0,0 +1,11 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v2.0.22 (MingW32) + +iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip +QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg +1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW +NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA +NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV +JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74= +=B6+8 +-----END PGP SIGNATURE----- diff --git a/lam/lib/3rdParty/composer/paragonie/random_compat/lib/random.php b/lam/lib/3rdParty/composer/paragonie/random_compat/lib/random.php new file mode 100644 index 00000000..c7731a56 --- /dev/null +++ b/lam/lib/3rdParty/composer/paragonie/random_compat/lib/random.php @@ -0,0 +1,32 @@ +buildFromDirectory(dirname(__DIR__).'/lib'); +rename( + dirname(__DIR__).'/lib/index.php', + dirname(__DIR__).'/lib/random.php' +); + +/** + * If we pass an (optional) path to a private key as a second argument, we will + * sign the Phar with OpenSSL. + * + * If you leave this out, it will produce an unsigned .phar! + */ +if ($argc > 1) { + if (!@is_readable($argv[1])) { + echo 'Could not read the private key file:', $argv[1], "\n"; + exit(255); + } + $pkeyFile = file_get_contents($argv[1]); + + $private = openssl_get_privatekey($pkeyFile); + if ($private !== false) { + $pkey = ''; + openssl_pkey_export($private, $pkey); + $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey); + + /** + * Save the corresponding public key to the file + */ + if (!@is_readable($dist.'/random_compat.phar.pubkey')) { + $details = openssl_pkey_get_details($private); + file_put_contents( + $dist.'/random_compat.phar.pubkey', + $details['key'] + ); + } + } else { + echo 'An error occurred reading the private key from OpenSSL.', "\n"; + exit(255); + } +} diff --git a/lam/lib/3rdParty/composer/paragonie/random_compat/psalm-autoload.php b/lam/lib/3rdParty/composer/paragonie/random_compat/psalm-autoload.php new file mode 100644 index 00000000..d71d1b81 --- /dev/null +++ b/lam/lib/3rdParty/composer/paragonie/random_compat/psalm-autoload.php @@ -0,0 +1,9 @@ + + + + + + + + + + + + + + + diff --git a/lam/lib/3rdParty/composer/php-http/message-factory/CHANGELOG.md b/lam/lib/3rdParty/composer/php-http/message-factory/CHANGELOG.md new file mode 100644 index 00000000..4711924c --- /dev/null +++ b/lam/lib/3rdParty/composer/php-http/message-factory/CHANGELOG.md @@ -0,0 +1,65 @@ +# Change Log + + +## 1.0.2 - 2015-12-19 + +### Added + +- Request and Response factory binding types to Puli + + +## 1.0.1 - 2015-12-17 + +### Added + +- Puli configuration and binding types + + +## 1.0.0 - 2015-12-15 + +### Added + +- Response Factory in order to be reused in Message and Server Message factories +- Request Factory + +### Changed + +- Message Factory extends Request and Response factories + + +## 1.0.0-RC1 - 2015-12-14 + +### Added + +- CS check + +### Changed + +- RuntimeException is thrown when the StreamFactory cannot write to the underlying stream + + +## 0.3.0 - 2015-11-16 + +### Removed + +- Client Context Factory +- Factory Awares and Templates + + +## 0.2.0 - 2015-11-16 + +### Changed + +- Reordered the parameters when creating a message to have the protocol last, +as its the least likely to need to be changed. + + +## 0.1.0 - 2015-06-01 + +### Added + +- Initial release + +### Changed + +- Helpers are renamed to templates diff --git a/lam/lib/3rdParty/composer/php-http/message-factory/LICENSE b/lam/lib/3rdParty/composer/php-http/message-factory/LICENSE new file mode 100644 index 00000000..8e2c4a0b --- /dev/null +++ b/lam/lib/3rdParty/composer/php-http/message-factory/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 PHP HTTP Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lam/lib/3rdParty/composer/php-http/message-factory/README.md b/lam/lib/3rdParty/composer/php-http/message-factory/README.md new file mode 100644 index 00000000..4654495a --- /dev/null +++ b/lam/lib/3rdParty/composer/php-http/message-factory/README.md @@ -0,0 +1,36 @@ +# PSR-7 Message Factory + +[![Latest Version](https://img.shields.io/github/release/php-http/message-factory.svg?style=flat-square)](https://github.com/php-http/message-factory/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Total Downloads](https://img.shields.io/packagist/dt/php-http/message-factory.svg?style=flat-square)](https://packagist.org/packages/php-http/message-factory) + +**Factory interfaces for PSR-7 HTTP Message.** + + +## Install + +Via Composer + +``` bash +$ composer require php-http/message-factory +``` + + +## Documentation + +Please see the [official documentation](http://php-http.readthedocs.org/en/latest/message-factory/). + + +## Contributing + +Please see [CONTRIBUTING](CONTRIBUTING.md) and [CONDUCT](CONDUCT.md) for details. + + +## Security + +If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org). + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/lam/lib/3rdParty/composer/php-http/message-factory/composer.json b/lam/lib/3rdParty/composer/php-http/message-factory/composer.json new file mode 100644 index 00000000..7c72febe --- /dev/null +++ b/lam/lib/3rdParty/composer/php-http/message-factory/composer.json @@ -0,0 +1,27 @@ +{ + "name": "php-http/message-factory", + "description": "Factory interfaces for PSR-7 HTTP Message", + "license": "MIT", + "keywords": ["http", "factory", "message", "stream", "uri"], + "homepage": "http://php-http.org", + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0" + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/php-http/message-factory/puli.json b/lam/lib/3rdParty/composer/php-http/message-factory/puli.json new file mode 100644 index 00000000..08d37627 --- /dev/null +++ b/lam/lib/3rdParty/composer/php-http/message-factory/puli.json @@ -0,0 +1,43 @@ +{ + "version": "1.0", + "binding-types": { + "Http\\Message\\MessageFactory": { + "description": "PSR-7 Message Factory", + "parameters": { + "depends": { + "description": "Optional class dependency which can be checked by consumers" + } + } + }, + "Http\\Message\\RequestFactory": { + "parameters": { + "depends": { + "description": "Optional class dependency which can be checked by consumers" + } + } + }, + "Http\\Message\\ResponseFactory": { + "parameters": { + "depends": { + "description": "Optional class dependency which can be checked by consumers" + } + } + }, + "Http\\Message\\StreamFactory": { + "description": "PSR-7 Stream Factory", + "parameters": { + "depends": { + "description": "Optional class dependency which can be checked by consumers" + } + } + }, + "Http\\Message\\UriFactory": { + "description": "PSR-7 URI Factory", + "parameters": { + "depends": { + "description": "Optional class dependency which can be checked by consumers" + } + } + } + } +} diff --git a/lam/lib/3rdParty/composer/php-http/message-factory/src/MessageFactory.php b/lam/lib/3rdParty/composer/php-http/message-factory/src/MessageFactory.php new file mode 100644 index 00000000..965aaa80 --- /dev/null +++ b/lam/lib/3rdParty/composer/php-http/message-factory/src/MessageFactory.php @@ -0,0 +1,12 @@ + + */ +interface MessageFactory extends RequestFactory, ResponseFactory +{ +} diff --git a/lam/lib/3rdParty/composer/php-http/message-factory/src/RequestFactory.php b/lam/lib/3rdParty/composer/php-http/message-factory/src/RequestFactory.php new file mode 100644 index 00000000..624e82f3 --- /dev/null +++ b/lam/lib/3rdParty/composer/php-http/message-factory/src/RequestFactory.php @@ -0,0 +1,34 @@ + + */ +interface RequestFactory +{ + /** + * Creates a new PSR-7 request. + * + * @param string $method + * @param string|UriInterface $uri + * @param array $headers + * @param resource|string|StreamInterface|null $body + * @param string $protocolVersion + * + * @return RequestInterface + */ + public function createRequest( + $method, + $uri, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ); +} diff --git a/lam/lib/3rdParty/composer/php-http/message-factory/src/ResponseFactory.php b/lam/lib/3rdParty/composer/php-http/message-factory/src/ResponseFactory.php new file mode 100644 index 00000000..2411ed3a --- /dev/null +++ b/lam/lib/3rdParty/composer/php-http/message-factory/src/ResponseFactory.php @@ -0,0 +1,35 @@ + + */ +interface ResponseFactory +{ + /** + * Creates a new PSR-7 response. + * + * @param int $statusCode + * @param string|null $reasonPhrase + * @param array $headers + * @param resource|string|StreamInterface|null $body + * @param string $protocolVersion + * + * @return ResponseInterface + */ + public function createResponse( + $statusCode = 200, + $reasonPhrase = null, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ); +} diff --git a/lam/lib/3rdParty/composer/php-http/message-factory/src/StreamFactory.php b/lam/lib/3rdParty/composer/php-http/message-factory/src/StreamFactory.php new file mode 100644 index 00000000..327a902f --- /dev/null +++ b/lam/lib/3rdParty/composer/php-http/message-factory/src/StreamFactory.php @@ -0,0 +1,25 @@ + + */ +interface StreamFactory +{ + /** + * Creates a new PSR-7 stream. + * + * @param string|resource|StreamInterface|null $body + * + * @return StreamInterface + * + * @throws \InvalidArgumentException If the stream body is invalid. + * @throws \RuntimeException If creating the stream from $body fails. + */ + public function createStream($body = null); +} diff --git a/lam/lib/3rdParty/composer/php-http/message-factory/src/UriFactory.php b/lam/lib/3rdParty/composer/php-http/message-factory/src/UriFactory.php new file mode 100644 index 00000000..f05e6252 --- /dev/null +++ b/lam/lib/3rdParty/composer/php-http/message-factory/src/UriFactory.php @@ -0,0 +1,24 @@ + + */ +interface UriFactory +{ + /** + * Creates an PSR-7 URI. + * + * @param string|UriInterface $uri + * + * @return UriInterface + * + * @throws \InvalidArgumentException If the $uri argument can not be converted into a valid URI. + */ + public function createUri($uri); +} diff --git a/lam/lib/3rdParty/composer/psr/http-client/CHANGELOG.md b/lam/lib/3rdParty/composer/psr/http-client/CHANGELOG.md new file mode 100644 index 00000000..171ac649 --- /dev/null +++ b/lam/lib/3rdParty/composer/psr/http-client/CHANGELOG.md @@ -0,0 +1,19 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.0.0 + +First stable release. No changes since 0.3.0. + +## 0.3.0 + +Added Interface suffix on exceptions + +## 0.2.0 + +All exceptions are in `Psr\Http\Client` namespace + +## 0.1.0 + +First release \ No newline at end of file diff --git a/lam/lib/3rdParty/composer/psr/http-client/LICENSE b/lam/lib/3rdParty/composer/psr/http-client/LICENSE new file mode 100644 index 00000000..cd5e0020 --- /dev/null +++ b/lam/lib/3rdParty/composer/psr/http-client/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2017 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lam/lib/3rdParty/composer/psr/http-client/README.md b/lam/lib/3rdParty/composer/psr/http-client/README.md new file mode 100644 index 00000000..e600b559 --- /dev/null +++ b/lam/lib/3rdParty/composer/psr/http-client/README.md @@ -0,0 +1,14 @@ +PSR Http Client +=============== + +This repository holds all interfaces/classes/traits related to +[PSR-18](http://www.php-fig.org/psr/psr-18/). + +Note that this is not an HTTP client implementation of its own. It is merely an +interface that describes an HTTP client. See +[the specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-18-http-client.md) +for more details. + +You can find implementations of the specification by looking for packages providing +the [psr/http-client-implementation](https://packagist.org/providers/psr/http-client-implementation) +virtual package. diff --git a/lam/lib/3rdParty/composer/psr/http-client/composer.json b/lam/lib/3rdParty/composer/psr/http-client/composer.json new file mode 100644 index 00000000..78cb39b6 --- /dev/null +++ b/lam/lib/3rdParty/composer/psr/http-client/composer.json @@ -0,0 +1,27 @@ +{ + "name": "psr/http-client", + "description": "Common interface for HTTP clients", + "keywords": ["psr", "psr-18", "http", "http-client"], + "homepage": "https://github.com/php-fig/http-client", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": "^7.0", + "psr/http-message": "^1.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/psr/http-client/src/ClientExceptionInterface.php b/lam/lib/3rdParty/composer/psr/http-client/src/ClientExceptionInterface.php new file mode 100644 index 00000000..aa0b9cf1 --- /dev/null +++ b/lam/lib/3rdParty/composer/psr/http-client/src/ClientExceptionInterface.php @@ -0,0 +1,10 @@ +=7.0.0", + "psr/http-message": "^1.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/psr/http-factory/src/RequestFactoryInterface.php b/lam/lib/3rdParty/composer/psr/http-factory/src/RequestFactoryInterface.php new file mode 100644 index 00000000..cb39a08b --- /dev/null +++ b/lam/lib/3rdParty/composer/psr/http-factory/src/RequestFactoryInterface.php @@ -0,0 +1,18 @@ +=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/psr/http-message/src/MessageInterface.php b/lam/lib/3rdParty/composer/psr/http-message/src/MessageInterface.php new file mode 100644 index 00000000..dd46e5ec --- /dev/null +++ b/lam/lib/3rdParty/composer/psr/http-message/src/MessageInterface.php @@ -0,0 +1,187 @@ +getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders(); + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($name); + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader($name); + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine($name); + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withHeader($name, $value); + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader($name, $value); + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return static + */ + public function withoutHeader($name); + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody(); + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return static + * @throws \InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body); +} diff --git a/lam/lib/3rdParty/composer/psr/http-message/src/RequestInterface.php b/lam/lib/3rdParty/composer/psr/http-message/src/RequestInterface.php new file mode 100644 index 00000000..a96d4fd6 --- /dev/null +++ b/lam/lib/3rdParty/composer/psr/http-message/src/RequestInterface.php @@ -0,0 +1,129 @@ +getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams(); + + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return static + */ + public function withQueryParams(array $query); + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles(); + + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array $uploadedFiles An array tree of UploadedFileInterface instances. + * @return static + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles); + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody(); + + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return static + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data); + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes(); + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute($name, $default = null); + + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return static + */ + public function withAttribute($name, $value); + + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return static + */ + public function withoutAttribute($name); +} diff --git a/lam/lib/3rdParty/composer/psr/http-message/src/StreamInterface.php b/lam/lib/3rdParty/composer/psr/http-message/src/StreamInterface.php new file mode 100644 index 00000000..f68f3912 --- /dev/null +++ b/lam/lib/3rdParty/composer/psr/http-message/src/StreamInterface.php @@ -0,0 +1,158 @@ + + * [user-info@]host[:port] + * + * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority(); + + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo(); + + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost(); + + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort(); + + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath(); + + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery(); + + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment(); + + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return static A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme($scheme); + + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return static A new instance with the specified user information. + */ + public function withUserInfo($user, $password = null); + + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return static A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ + public function withHost($host); + + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return static A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ + public function withPort($port); + + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return static A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ + public function withPath($path); + + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return static A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ + public function withQuery($query); + + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return static A new instance with the specified fragment. + */ + public function withFragment($fragment); + + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString(); +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/CHANGELOG.md b/lam/lib/3rdParty/composer/ramsey/uuid/CHANGELOG.md new file mode 100644 index 00000000..3965fff2 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/CHANGELOG.md @@ -0,0 +1,376 @@ +# ramsey/uuid Changelog + +## 3.8.0 + +_Released: 2018-07-19_ + + * Add support for determining MAC address on FreeBSD systems ([#212](https://github.com/ramsey/uuid/pull/212)) + * Add a polyfill for PHP ctype functions to support systems where the ctype functions are not part of the PHP build ([#223](https://github.com/ramsey/uuid/pull/223)) + * Improve validation to disallow UUIDs with a trailing newline character ([#225](https://github.com/ramsey/uuid/pull/225)) + * Add annotations for thrown exceptions for improved IDE hinting ([#232](https://github.com/ramsey/uuid/pull/232)) + * Improve documentation, testing, and project metadata (i.e. `.gitattributes`, etc.) + +## 3.7.3 + +_Released: 2018-01-19_ + + * In rare cases, when using `glob()` to find `/sys/class/net/*/address` files on Linux, `glob()` encountered errors, returning `false` instead of an empty array, causing `array_map()` to emit warnings since its second parameter was not an array; this release gracefully handles cases where `glob()` returns `false` [#203](https://github.com/ramsey/uuid/issues/203) + * Fixed an off-by-one error in `DefaultTimeGenerator` and switching to `random_int()` from `mt_rand()` for better random numbers [#206](https://github.com/ramsey/uuid/pull/206) + +## 3.7.2 + +_Released: 2018-01-13_ + + * On Linux, first check sysfs to determine node identifier; this provides a reliable way to identify the node on Docker images, etc. [#185](https://github.com/ramsey/uuid/pull/185) + +## 3.7.1 + +_Released: 2017-09-22_ + + * Use `random_bytes()` when generating random nodes + * Set the multicast bit for random nodes, according to RFC 4122, §4.5, [#170](https://github.com/ramsey/uuid/pull/170), [#171](https://github.com/ramsey/uuid/pull/171), [#182](https://github.com/ramsey/uuid/pull/182) + +## 3.7.0 + +_Released: 2017-08-04_ + + * Add UUID version constants [#173](https://github.com/ramsey/uuid/issues/173), [#177](https://github.com/ramsey/uuid/pull/177) + * `Uuid::UUID_TYPE_TIME` + * `Uuid::UUID_TYPE_IDENTIFIER` + * `Uuid::UUID_TYPE_HASH_MD5` + * `Uuid::UUID_TYPE_RANDOM` + * `Uuid::UUID_TYPE_HASH_SHA1` + +## 3.6.1 + +_Released: 2017-03-26_ + + * Optimize UUID string decoding [#164](https://github.com/ramsey/uuid/pull/164) + +## 3.6.0 + +_Released: 2017-03-18_ + + * Add `InvalidUuidStringException`, thrown when attempting to decode an invalid string UUID; this does not introduce any BC issues, since the new exception inherits from the previously used `InvalidArgumentException` [#162](https://github.com/ramsey/uuid/pull/162) + * Improve memory usage when generating large quantities of UUIDs (use `str_pad()` and `dechex()` instead of `sprintf()`) [#160](https://github.com/ramsey/uuid/pull/160) + * Minor test and documentation updates + +## 3.5.2 + +_Released: 2016-11-22_ + + * Improved test coverage. + +## 3.5.1 + +_Released: 2016-10-02_ + + * Fixed issue where same UUIDs were not treated as equal with mixed case ([#131](https://github.com/ramsey/uuid/issues/131), [#137](https://github.com/ramsey/uuid/pull/137)). + * Test cleanup. + +## 3.5.0 + +_Released: 2016-08-02_ + + * Add `OrderedTimeCodec` to store UUID in an optimized way for InnoDB ([#117](https://github.com/ramsey/uuid/issues/117), [#118](https://github.com/ramsey/uuid/pull/118)). + * Fixed `RandomNodeProvider` to prevent invalid node generation ([#129](https://github.com/ramsey/uuid/pull/129)). + * Cache failed attempt to retrieve system node to avoid multiple system calls ([#107](https://github.com/ramsey/uuid/issues/107), [#121](https://github.com/ramsey/uuid/pull/121)). + * Various test improvements. + +## 3.4.1 + +_Released: 2016-04-23_ + + * Fixed test that violated a PHP CodeSniffer rule, breaking the build. + +## 3.4.0 + +_Released: 2016-04-23_ + + * Add `TimestampFirstCombCodec` and `TimestampLastCombCodec` codecs. + * Improve logic of `CombGenerator` for COMB sequential UUIDs. + * Significantly improved test coverage. + +## 3.3.0 + +_Released: 2016-03-22_ + + * Drop the use of OpenSSL as a fallback and use [paragonie/random_compat][] to support RandomBytesGenerator in versions of PHP earlier than 7.0. This addresses and fixes the [collision issue][]. + * Improved test coverage. + * Update code to conduct to version 1.4 of the Contributor Covenant. + +## 3.2.0 + +_Released: 2016-02-17_ + + * Add random generator option for use for the PECL libsodium extension. + * Updates to test infrastructure. + +## 3.1.0 + +_Released: 2015-12-17_ + + * Uuid objects now may be properly serialized/unserialized. + * Update build environments for testing on Travis CI. + +## 3.0.1 + +_Released: 2015-10-21_ + + * Add project [Contributor Code of Conduct](https://github.com/ramsey/uuid/blob/master/CONDUCT.md) + * Modify Travis CI builds to run tests on multiple CPU architectures + * Clean up code, tests, and documentation + +## 3.0.0 + +_Released: 2015-09-28_ + +The 3.0.0 release represents a significant step for the ramsey/uuid library. While the simple and familiar API used in previous versions remains intact, this release provides greater flexibility to integrators, including the ability to inject your own number generators, UUID codecs, node and time providers, and more. + + * BREAK: The root namespace for this package has changed from "Rhumsaa" to "Ramsey." In most cases, simply making this change in your applications is the only upgrade path you will need. Everything else should work as expected. + * BREAK: The UUID [Doctrine field type](http://doctrine-dbal.readthedocs.org/en/latest/reference/types.html) has been moved to [ramsey/uuid-doctrine](https://github.com/ramsey/uuid-doctrine). + * BREAK: The `uuid` console application has been moved to [ramsey/uuid-console](https://github.com/ramsey/uuid-console). + * BREAK: The `Uuid::VERSION` package version constant has been removed. + * See also the release notes for [3.0.0-alpha1][300-alpha1], [3.0.0-alpha2][300-alpha2], [3.0.0-alpha3][300-alpha3], and [3.0.0-beta1][300-beta1]. + +[300-alpha1]: https://github.com/ramsey/uuid/blob/master/CHANGELOG.md#300-alpha1 +[300-alpha2]: https://github.com/ramsey/uuid/blob/master/CHANGELOG.md#300-alpha2 +[300-alpha3]: https://github.com/ramsey/uuid/blob/master/CHANGELOG.md#300-alpha3 +[300-beta1]: https://github.com/ramsey/uuid/blob/master/CHANGELOG.md#300-beta1 + +## 3.0.0-beta1 + +_Released: 2015-08-31_ + + * Improve GUID support to ensure that: + * On little endian (LE) architectures, the byte order of the first three fields is LE. + * On big endian (BE) architectures, it is the same as a GUID. + * String representation is always the same. + * Fix exception message for `DegradedNumberConverter::fromHex()`. + * Add Scrutinizer configuration to run code-quality builds through Scrutinizer. + * Auto-fix Scrutinizer issues. + * Fix support URLs in composer.json to point to the correct GitHub repository. + +## 3.0.0-alpha3 + +_Released: 2015-07-28_ + + * Time generator improvements: + * Enabled use of custom TimeGenerator implementations. + * BREAK: Removed now unnecessary `timeConverter` and `timeProvider` properties, setters, and getters in both `FeatureSet` and `UuidFactory` as those are now exclusively used by the default `TimeGenerator`. + * Added a `setTimeGenerator` method on `UuidFactory` to override the default time generator. + * Add option to enable `PeclUuidTimeGenerator` via `FeatureSet`. + +## 3.0.0-alpha2 + +_Released: 2015-07-28_ + + * BREAK: Removed `PeclUuidFactory` in favor of using pecl-uuid with generators. + * NEW: Refactored time-based (version 1) UUIDs into a `TimeGeneratorInterface` to allow for other sources to generate version 1 UUIDs in this library. + * NEW: Added `PeclUuidTimeGenerator` and `PeclUuidRandomGenerator` for creating version 1 or version 4 UUIDs using the pecl-uuid extension. + * NEW: Add `RandomBytesGenerator` for use with PHP 7. ramsey/uuid will default to use this generator when running on PHP 7. + * `RandomLibAdapter` now defaults to a medium-strength generator with [ircmaxell/random-lib](https://github.com/ircmaxell/RandomLib). This is configurable, so other generator strengths may be used. + * Migrated to the Travis CI container-based infrastructure for builds. + * Documentation updates and corrections. + +## 3.0.0-alpha1 + +_Released: 2015-07-16_ + + * BREAK: The root namespace for this package has changed from "Rhumsaa" to "Ramsey." In most cases, simply making this change in your applications is the only upgrade path you will need. Everything else should work as expected. + * BREAK: The UUID [Doctrine field type](http://doctrine-dbal.readthedocs.org/en/latest/reference/types.html) has been moved to [ramsey/uuid-doctrine](https://github.com/ramsey/uuid-doctrine). + * BREAK: The `uuid` console application has been moved to [ramsey/uuid-console](https://github.com/ramsey/uuid-console). + * BREAK: The `Uuid::VERSION` package version constant has been removed. + * NEW: The `Uuid` class is no longer marked as `final`. Everything is now based around interfaces and factories, allowing you to use this package as a base to implement other kinds of UUIDs with different dependencies. + * NEW: Through setting dependencies on `UuidFactory` and/or extending `FeatureSet`, you may override any package defaults, injecting your own dependencies. + * NEW: For random number generation, in addition to `OpenSslGenerator` (used if `openssl_random_pseudo_bytes()` is present) and the fallback `MtRandGenerator`, you may use the bundled `CombGenerator` for sequential UUIDs or the `RandomLibAdapter` if using [ircmaxell/random-lib](https://github.com/ircmaxell/RandomLib). + * NEW: In addition to the default UUID generation, this library also supports GUID generation by configuring a `FeatureSet` to use GUIDs. + * NEW: While the interface to create UUIDs hasn't changed, if using this package on a 32-bit system, you will now receive an object of type `DegradedUuid` (which extends `Uuid`, which implements `UuidInterface`). + * NEW: All UUIDs are now [JsonSerializable](http://php.net/JsonSerializable). + +## 2.9.0 + +_Released: 2016-03-22_ + + * Drop support for OpenSSL in favor of [paragonie/random_compat][]. This addresses and fixes the [collision issue][]. + +## 2.8.4 + +_Released: 2015-12-17_ + + * Add support for symfony/console v3. + * Update build matrix to run Travis CI tests on PHP 7 & with lowest package versions. + +## 2.8.3 + +_Released: 2015-08-31_ + + * Fix exception message in `Uuid::calculateUuidTime()`. + * Update composer.json to reflect new repository and package name. + +## 2.8.2 + +_Released: 2015-07-23_ + + * Ensure the release tag makes it into the rhumsaa/uuid package. + * Minor documentation changes. + +## 2.8.1 + +_Released: 2015-06-16_ + + * Use `passthru()` and output buffering in `getIfconfig()`. + * Cache the system node in a static variable so that we process it only once per runtime. + * Set ramsey/uuid as a replacement for rhumsaa/uuid in composer.json. + * Documentation updates and corrections. + +## 2.8.0 + +_Released: 2014-11-09_ + + * Added static `fromInteger()` method to create UUIDs from string integer or `\Moontoast\Math\BigNumber`. + * Friendlier Doctrine conversion to Uuid or string. + * Documentation fixes. + +## 2.7.4 + +_Released: 2014-10-29_ + + * Changed loop in `generateBytes()` from `foreach` to `for`; see #33 + * Use `toString()` in README examples to avoid confusion + * Exclude build/development tools from releases using .gitattributes + * Set timezone properly for tests + +## 2.7.3 + +_Released: 2014-08-27_ + + * Fixed upper range for `mt_rand` used in version 4 UUIDs + +## 2.7.2 + +_Released: 2014-07-28_ + + * Upgraded to PSR-4 autoloading + * Testing upgrades: + * Testing against PHP 5.6 + * Testing with PHPUnit 4 + * Using Coveralls.io to generate code coverage reports + * Documentation fixes + +## 2.7.1 + +_Released: 2014-02-19_ + + * Moved moontoast/math and symfony/console to require-dev; fixes #20 + * Now supporting symfony/console for 2.3 (LTS version); fixes #21 + * Updated tests to run even when dev packages are not installed (skips tests if requirements are not met) + +## 2.7.0 + +_Released: 2014-01-31_ + + * Moved UUID validation regex pattern into constant for external use (`Uuid::VALID_PATTERN`) + +## 2.6.1 + +_Released: 2014-01-27_ + + * Fixed bug where `uuid` console application could not find the Composer autoloader when installed in another project + +## 2.6.0 + +_Released: 2014-01-17_ + + * Introduced `uuid` console application for generating and decoding UUIDs from CLI (run `./bin/uuid` for details) + * Added `Uuid::getInteger()` to retrieve a Moontoast\Math\BigNumber representation of the 128-bit integer representing the UUID + * Added `Uuid::getHex()` to retrieve the hexadecimal representation of the UUID + * Now using netstat on Linux to capture the node for a version 1 UUID + * Now requiring Moontoast\Math as part of the regular package requirements, not just the dev requirements + +## 2.5.0 + +_Released: 2013-10-30_ + + * Using `openssl_random_pseudo_bytes()`, if available, to generate random bytes, by merging in PR #15 from @dfreudenberger + * Fixed test for Rhumsaa\Uuid\Doctrine\UuidType, by merging in PR #17 from @dfreudenberger + * Documentation fixes + +## 2.4.0 + +_Released: 2013-07-29_ + + * `Uuid::getVersion()` now returns null if the UUID isn't an RFC 4122 variant + * `Uuid::fromString()` now supports a 128-bit integer formatted as a hexadecimal string (UUID without dashes) + * Tests have been greatly enhanced, borrowing from the Python UUID library + +## 2.3.0 + +_Released: 2013-07-16_ + + * Added `Uuid::fromBytes()` by merging in PR #14 from @asm89 + +## 2.2.0 + +_Released: 2013-07-04_ + + * Added `Doctrine\UuidType::requiresSQLCommentHint()` method by merging in PR #13 from @zerrvox + * Removed `"minimum-stability": "dev"` from composer.json + +## 2.1.2 + +_Released: 2013-07-03_ + + * @ericthelin found cases where the system node was coming back with uppercase hexadecimal digits; this ensures that case in the node is converted to lowercase + +## 2.1.1 + +_Released: 2013-04-29_ + + * Fixed NIL bug in `Uuid::isValid()` method, reported by @ocubom in PR #11 + +## 2.1.0 + +_Released: 2013-04-15_ + + * Added static `Uuid::isValid()` method for checking whether a string is a valid UUID + +## 2.0.0 + +_Released: 2013-02-11_ + + * Break: `Uuid` class is now marked as "final" + * Break: `Uuid::getLeastSignificantBits()` no longer returns an integer on 64-bit platforms; it requires `moontoast/math` + * Break: `Uuid::getMostSignificantBits()` no longer returns an integer on 64-bit platforms; it requires `moontoast/math` + * Break: Moved `UnsupportedOperationException` to the `Exception` subnamespace + * Added support for 32-bit platforms + * Added generated API documentation to the repository + +## 1.1.2 + +_Released: 2012-11-29_ + + * Relaxed Doctrine type conversion rules + +## 1.1.1 + +_Released: 2012-08-27_ + + * Removed `final` keyword from `Uuid` class + +## 1.1.0 + +_Released: 2012-08-06_ + + * Added `Doctrine\UuidType` as a field mapping type for the Doctrine Database Abstraction Layer (DBAL) + * Improved tests and code coverage + +## 1.0.0 + +_Released: 2012-07-19_ + + * Initial release + + +[paragonie/random_compat]: https://github.com/paragonie/random_compat +[collision issue]: https://github.com/ramsey/uuid/issues/80 diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/CODE_OF_CONDUCT.md b/lam/lib/3rdParty/composer/ramsey/uuid/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..9c207259 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project maintainer at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/CONTRIBUTING.md b/lam/lib/3rdParty/composer/ramsey/uuid/CONTRIBUTING.md new file mode 100644 index 00000000..4cde9b84 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/CONTRIBUTING.md @@ -0,0 +1,75 @@ +# Contributing + +Contributions are welcome. We accept pull requests on [GitHub](https://github.com/ramsey/uuid). + +This project adheres to a [Contributor Code of Conduct](https://github.com/ramsey/uuid/blob/master/CODE_OF_CONDUCT.md). By participating in this project and its community, you are expected to uphold this code. + +## Team members + +* [Ben Ramsey](https://github.com/ramsey) - original author and maintainer +* [Marijn Huizendveld](https://github.com/marijn) - contributor, author of UUID type definition for Doctrine DBAL +* [Thibaud Fabre](https://github.com/aztech-dev) - contributor, lead developer for version 3.0.0 re-architecture + +## Communication Channels + +You can find help and discussion in the following places: + +* GitHub Issues: +* Wiki: + +## Reporting Bugs + +Bugs are tracked in our project's [issue tracker](https://github.com/ramsey/uuid/issues). + +When submitting a bug report, please include enough information for us to reproduce the bug. A good bug report includes the following sections: + +* Expected outcome +* Actual outcome +* Steps to reproduce, including sample code +* Any other information that will help us debug and reproduce the issue, including stack traces, system/environment information, and screenshots + +**Please do not include passwords or any personally identifiable information in your bug report and sample code.** + +## Fixing Bugs + +We welcome pull requests to fix bugs! + +If you see a bug report that you'd like to fix, please feel free to do so. Following the directions and guidelines described in the "Adding New Features" section below, you may create bugfix branches and send us pull requests. + +## Adding New Features + +If you have an idea for a new feature, it's a good idea to check out our [issues](https://github.com/ramsey/uuid/issues) or active [pull requests](https://github.com/ramsey/uuid/pulls) first to see if the feature is already being worked on. If not, feel free to submit an issue first, asking whether the feature is beneficial to the project. This will save you from doing a lot of development work only to have your feature rejected. We don't enjoy rejecting your hard work, but some features just don't fit with the goals of the project. + +When you do begin working on your feature, here are some guidelines to consider: + +* Your pull request description should clearly detail the changes you have made. We will use this description to add to our CHANGELOG. If there is no description or it does not adequately describe your feature, we will ask you to update the description. +* We following the **[PSR-2 coding standard](http://www.php-fig.org/psr/psr-2/)**. Please ensure your code does, too. +* Please **write tests** for any new features you add. +* Please **ensure that tests pass** before submitting your pull request. We have Travis CI automatically running tests for pull requests. However, running the tests locally will help save time. +* **Use topic/feature branches.** Please do not ask us to pull from your master branch. +* **Submit one feature per pull request.** If you have multiple features you wish to submit, please break them up into separate pull requests. +* **Send coherent history**. Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. + +## Running Tests + +The following tests must pass before we will accept a pull request. If any of these do not pass, it will result in a complete build failure. Before you can run these, be sure to `composer install`. + +``` +composer test +``` + +### Locally Test With Emulated MIPS Architecture + +The following commands use [Vagrant](https://www.vagrantup.com/) to start an Ubuntu VM, install necessary dependencies, and then run the `tools/run-tests.sh` script that will download a Docker image emulating the MIPS architecture. This is especially helpful for testing UUID generation in a big-endian environment. + +``` +vagrant init ubuntu/trusty64 +vagrant up +vagrant ssh +sudo apt-get install docker.io qemu-user-static php5-cli php5-curl +cd /vagrant +curl -sS https://getcomposer.org/installer | php +php composer.phar install --no-interaction --prefer-dist +mkdir -p build/logs +ARCH=mips PHP_VERSION=5.6.14 TRAVIS_BUILD_DIR=/vagrant ./tools/run-tests.sh +``` diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/LICENSE b/lam/lib/3rdParty/composer/ramsey/uuid/LICENSE new file mode 100644 index 00000000..753a4c9c --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012-2018 Ben Ramsey + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/README.md b/lam/lib/3rdParty/composer/ramsey/uuid/README.md new file mode 100644 index 00000000..b6557abb --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/README.md @@ -0,0 +1,159 @@ +# ramsey/uuid + +_NOTICE: Formerly known as `rhumsaa/uuid`, The package and namespace names have changed to `ramsey/uuid` and `Ramsey\Uuid`, respectively._ + +[![Source Code][badge-source]][source] +[![Latest Version][badge-release]][release] +[![Software License][badge-license]][license] +[![Build Status][badge-build]][build] +[![Coverage Status][badge-coverage]][coverage] +[![Total Downloads][badge-downloads]][downloads] + +ramsey/uuid is a PHP 5.4+ library for generating and working with [RFC 4122][rfc4122] version 1, 3, 4, and 5 universally unique identifiers (UUID). + +This project adheres to a [Contributor Code of Conduct][conduct]. By participating in this project and its community, you are expected to uphold this code. + + +## About + +From [Wikipedia](http://en.wikipedia.org/wiki/Universally_unique_identifier): + +> The intent of UUIDs is to enable distributed systems to uniquely identify information without significant central coordination. In this context the word unique should be taken to mean "practically unique" rather than "guaranteed unique". Since the identifiers have a finite size, it is possible for two differing items to share the same identifier. The identifier size and generation process need to be selected so as to make this sufficiently improbable in practice. Anyone can create a UUID and use it to identify something with reasonable confidence that the same identifier will never be unintentionally created by anyone to identify something else. Information labeled with UUIDs can therefore be later combined into a single database without needing to resolve identifier (ID) conflicts. + +Much inspiration for this library came from the [Java][javauuid] and [Python][pyuuid] UUID libraries. + + +## Installation + +The preferred method of installation is via [Packagist][] and [Composer][]. Run the following command to install the package and add it as a requirement to your project's `composer.json`: + +```bash +composer require ramsey/uuid +``` + + +## Upgrading from 2.x to 3.x + +While we have made significant internal changes to the library, we have made every effort to ensure a seamless upgrade path from the 2.x series of this library to 3.x. + +One major breaking change is the transition from the `Rhumsaa` root namespace to `Ramsey`. In most cases, all you will need is to change the namespace to `Ramsey` in your code, and everything will "just work." + +Here are full details on the breaking changes to the public API of this library: + +1. All namespace references of `Rhumsaa` have changed to `Ramsey`. Simply change the namespace to `Ramsey` in your code and everything should work. +2. The console application has moved to [ramsey/uuid-console](https://packagist.org/packages/ramsey/uuid-console). If using the console functionality, use Composer to require `ramsey/uuid-console`. +3. The Doctrine field type mapping has moved to [ramsey/uuid-doctrine](https://packagist.org/packages/ramsey/uuid-doctrine). If using the Doctrine functionality, use Composer to require `ramsey/uuid-doctrine`. + + +## What to do if you see a "rhumsaa/uuid is abandoned" message + +When installing your project's dependencies using Composer, you might see the following message: + +``` +Package rhumsaa/uuid is abandoned, you should avoid using it. Use ramsey/uuid instead. +``` + +Don't panic. Simply execute the following commands with Composer: + +``` bash +composer remove rhumsaa/uuid +composer require ramsey/uuid=^2.9 +``` + +After doing so, you will have the latest ramsey/uuid package in the 2.x series, and there will be no need to modify any code; the namespace in the 2.x series is still `Rhumsaa`. + + +## Requirements + +Some methods in this library have requirements due to integer size restrictions on 32-bit and 64-bit builds of PHP. A 64-bit build of PHP and the [Moontoast\Math][] library are recommended. However, this library is designed to work on 32-bit builds of PHP without Moontoast\Math, with some degraded functionality. Please check the API documentation for more information. + +If a particular requirement is not present, then an `UnsatisfiedDependencyException` is thrown, allowing one to catch a bad call in an environment where the call is not supported and gracefully degrade. + + +## API documentation + +The [latest class API documentation][apidocs] is available online. + +This project uses [ApiGen](http://apigen.org/) to generate this documentation. To generate the documentation on your own, install dev dependencies and run the following command from the root of the project: + +``` +composer build-docs +``` + +This will generate documentation in the `build/apidocs/` folder. + + +## Examples + +See the [cookbook on the wiki][wiki-cookbook] for more examples and approaches to specific use-cases. + +```php +toString() . "\n"; // i.e. e4eaaaf2-d142-11e1-b3e4-080027620cdd + + // Generate a version 3 (name-based and hashed with MD5) UUID object + $uuid3 = Uuid::uuid3(Uuid::NAMESPACE_DNS, 'php.net'); + echo $uuid3->toString() . "\n"; // i.e. 11a38b9a-b3da-360f-9353-a5a725514269 + + // Generate a version 4 (random) UUID object + $uuid4 = Uuid::uuid4(); + echo $uuid4->toString() . "\n"; // i.e. 25769c6c-d34d-4bfe-ba98-e0ee856f3e7a + + // Generate a version 5 (name-based and hashed with SHA1) UUID object + $uuid5 = Uuid::uuid5(Uuid::NAMESPACE_DNS, 'php.net'); + echo $uuid5->toString() . "\n"; // i.e. c4a760a8-dbcf-5254-a0d9-6a4474bd1b62 + +} catch (UnsatisfiedDependencyException $e) { + + // Some dependency was not met. Either the method cannot be called on a + // 32-bit system, or it can, but it relies on Moontoast\Math to be present. + echo 'Caught exception: ' . $e->getMessage() . "\n"; + +} +``` + + +## Contributing + +Contributions are welcome! Please read [CONTRIBUTING][] for details. + + +## Copyright and license + +The ramsey/uuid library is copyright © [Ben Ramsey](https://benramsey.com/) and licensed for use under the MIT License (MIT). Please see [LICENSE][] for more information. + + + +[rfc4122]: http://tools.ietf.org/html/rfc4122 +[conduct]: https://github.com/ramsey/uuid/blob/master/CODE_OF_CONDUCT.md +[javauuid]: http://docs.oracle.com/javase/6/docs/api/java/util/UUID.html +[pyuuid]: http://docs.python.org/3/library/uuid.html +[packagist]: https://packagist.org/packages/ramsey/uuid +[composer]: http://getcomposer.org/ +[moontoast\math]: https://packagist.org/packages/moontoast/math +[apidocs]: http://docs.benramsey.com/ramsey-uuid/latest/ +[wiki-cookbook]: https://github.com/ramsey/uuid/wiki/Ramsey%5CUuid-Cookbook +[contributing]: https://github.com/ramsey/uuid/blob/master/CONTRIBUTING.md + +[badge-source]: https://img.shields.io/badge/source-ramsey/uuid-blue.svg?style=flat-square +[badge-release]: https://img.shields.io/packagist/v/ramsey/uuid.svg?style=flat-square +[badge-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square +[badge-build]: https://img.shields.io/travis/ramsey/uuid/master.svg?style=flat-square +[badge-coverage]: https://img.shields.io/coveralls/ramsey/uuid/master.svg?style=flat-square +[badge-downloads]: https://img.shields.io/packagist/dt/ramsey/uuid.svg?style=flat-square + +[source]: https://github.com/ramsey/uuid +[release]: https://packagist.org/packages/ramsey/uuid +[license]: https://github.com/ramsey/uuid/blob/master/LICENSE +[build]: https://travis-ci.org/ramsey/uuid +[coverage]: https://coveralls.io/r/ramsey/uuid?branch=master +[downloads]: https://packagist.org/packages/ramsey/uuid diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/composer.json b/lam/lib/3rdParty/composer/ramsey/uuid/composer.json new file mode 100644 index 00000000..952120cd --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/composer.json @@ -0,0 +1,80 @@ +{ + "name": "ramsey/uuid", + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "type": "library", + "keywords": ["uuid", "identifier", "guid"], + "homepage": "https://github.com/ramsey/uuid", + "license": "MIT", + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + }, + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + } + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid" + }, + "require": { + "php": "^5.4 || ^7.0", + "paragonie/random_compat": "^1.0|^2.0|9.99.99", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "codeception/aspect-mock": "^1.0 | ~2.0.0", + "doctrine/annotations": "~1.2.0", + "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ~2.1.0", + "ircmaxell/random-lib": "^1.1", + "jakub-onderka/php-parallel-lint": "^0.9.0", + "mockery/mockery": "^0.9.9", + "moontoast/math": "^1.1", + "php-mock/php-mock-phpunit": "^0.3|^1.1", + "phpunit/phpunit": "^4.7|^5.0|^6.5", + "squizlabs/php_codesniffer": "^2.3" + }, + "suggest": { + "ext-ctype": "Provides support for PHP Ctype functions", + "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type.", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid" + }, + "autoload": { + "psr-4": {"Ramsey\\Uuid\\": "src/"} + }, + "autoload-dev": { + "psr-4": {"Ramsey\\Uuid\\Test\\": "tests/"} + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "scripts": { + "lint": "parallel-lint src tests", + "phpunit": "phpunit --verbose --colors=always", + "phpcs": "phpcs src tests --standard=psr2 -sp --colors", + "test": [ + "@lint", + "@phpunit", + "@phpcs" + ] + }, + "config": { + "sort-packages": true + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/BinaryUtils.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/BinaryUtils.php new file mode 100644 index 00000000..f04a9d9c --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/BinaryUtils.php @@ -0,0 +1,43 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Builder; + +use Ramsey\Uuid\Codec\CodecInterface; +use Ramsey\Uuid\Converter\NumberConverterInterface; +use Ramsey\Uuid\Uuid; + +/** + * DefaultUuidBuilder is the default UUID builder for ramsey/uuid; it builds + * instances of Uuid objects + */ +class DefaultUuidBuilder implements UuidBuilderInterface +{ + /** + * @var NumberConverterInterface + */ + private $converter; + + /** + * Constructs the DefaultUuidBuilder + * + * @param NumberConverterInterface $converter The number converter to use when constructing the Uuid + */ + public function __construct(NumberConverterInterface $converter) + { + $this->converter = $converter; + } + + /** + * Builds a Uuid + * + * @param CodecInterface $codec The codec to use for building this Uuid + * @param array $fields An array of fields from which to construct the Uuid; + * see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure. + * @return Uuid + */ + public function build(CodecInterface $codec, array $fields) + { + return new Uuid($fields, $this->converter, $codec); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Builder/DegradedUuidBuilder.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Builder/DegradedUuidBuilder.php new file mode 100644 index 00000000..7edb6deb --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Builder/DegradedUuidBuilder.php @@ -0,0 +1,53 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Builder; + +use Ramsey\Uuid\Codec\CodecInterface; +use Ramsey\Uuid\Converter\NumberConverterInterface; +use Ramsey\Uuid\DegradedUuid; + +/** + * DegradedUuidBuilder builds instances of DegradedUuid + */ +class DegradedUuidBuilder implements UuidBuilderInterface +{ + /** + * @var NumberConverterInterface + */ + private $converter; + + /** + * Constructs the DegradedUuidBuilder + * + * @param NumberConverterInterface $converter The number converter to use when constructing the DegradedUuid + */ + public function __construct(NumberConverterInterface $converter) + { + $this->converter = $converter; + } + + /** + * Builds a DegradedUuid + * + * @param CodecInterface $codec The codec to use for building this DegradedUuid + * @param array $fields An array of fields from which to construct the DegradedUuid; + * see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure. + * @return DegradedUuid + */ + public function build(CodecInterface $codec, array $fields) + { + return new DegradedUuid($fields, $this->converter, $codec); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Builder/UuidBuilderInterface.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Builder/UuidBuilderInterface.php new file mode 100644 index 00000000..e4e99010 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Builder/UuidBuilderInterface.php @@ -0,0 +1,34 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Builder; + +use Ramsey\Uuid\Codec\CodecInterface; +use Ramsey\Uuid\UuidInterface; + +/** + * UuidBuilderInterface builds instances UuidInterface + */ +interface UuidBuilderInterface +{ + /** + * Builds an instance of a UuidInterface + * + * @param CodecInterface $codec The codec to use for building this UuidInterface instance + * @param array $fields An array of fields from which to construct a UuidInterface instance; + * see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure. + * @return UuidInterface + */ + public function build(CodecInterface $codec, array $fields); +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/CodecInterface.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/CodecInterface.php new file mode 100644 index 00000000..6ea20544 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/CodecInterface.php @@ -0,0 +1,58 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Codec; + +use Ramsey\Uuid\UuidInterface; + +/** + * CodecInterface represents a UUID coder-decoder + */ +interface CodecInterface +{ + /** + * Encodes a UuidInterface as a string representation of a UUID + * + * @param UuidInterface $uuid + * @return string Hexadecimal string representation of a UUID + */ + public function encode(UuidInterface $uuid); + + /** + * Encodes a UuidInterface as a binary representation of a UUID + * + * @param UuidInterface $uuid + * @return string Binary string representation of a UUID + */ + public function encodeBinary(UuidInterface $uuid); + + /** + * Decodes a string representation of a UUID into a UuidInterface object instance + * + * @param string $encodedUuid + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + public function decode($encodedUuid); + + /** + * Decodes a binary representation of a UUID into a UuidInterface object instance + * + * @param string $bytes + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + * @throws \InvalidArgumentException if string has not 16 characters + */ + public function decodeBytes($bytes); +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/GuidStringCodec.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/GuidStringCodec.php new file mode 100644 index 00000000..864980b3 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/GuidStringCodec.php @@ -0,0 +1,102 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Codec; + +use Ramsey\Uuid\UuidInterface; + +/** + * GuidStringCodec encodes and decodes globally unique identifiers (GUID) + * + * @link https://en.wikipedia.org/wiki/Globally_unique_identifier + */ +class GuidStringCodec extends StringCodec +{ + /** + * Encodes a UuidInterface as a string representation of a GUID + * + * @param UuidInterface $uuid + * @return string Hexadecimal string representation of a GUID + */ + public function encode(UuidInterface $uuid) + { + $components = array_values($uuid->getFieldsHex()); + + // Swap byte-order on the first three fields + $this->swapFields($components); + + return vsprintf( + '%08s-%04s-%04s-%02s%02s-%012s', + $components + ); + } + + /** + * Encodes a UuidInterface as a binary representation of a GUID + * + * @param UuidInterface $uuid + * @return string Binary string representation of a GUID + */ + public function encodeBinary(UuidInterface $uuid) + { + $components = array_values($uuid->getFieldsHex()); + + return hex2bin(implode('', $components)); + } + + /** + * Decodes a string representation of a GUID into a UuidInterface object instance + * + * @param string $encodedUuid + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + public function decode($encodedUuid) + { + $components = $this->extractComponents($encodedUuid); + + $this->swapFields($components); + + return $this->getBuilder()->build($this, $this->getFields($components)); + } + + /** + * Decodes a binary representation of a GUID into a UuidInterface object instance + * + * @param string $bytes + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + public function decodeBytes($bytes) + { + // Specifically call parent::decode to preserve correct byte order + return parent::decode(bin2hex($bytes)); + } + + /** + * Swaps fields to support GUID byte order + * + * @param array $components An array of UUID components (the UUID exploded on its dashes) + * @return void + */ + protected function swapFields(array &$components) + { + $hex = unpack('H*', pack('L', hexdec($components[0]))); + $components[0] = $hex[1]; + $hex = unpack('H*', pack('S', hexdec($components[1]))); + $components[1] = $hex[1]; + $hex = unpack('H*', pack('S', hexdec($components[2]))); + $components[2] = $hex[1]; + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/OrderedTimeCodec.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/OrderedTimeCodec.php new file mode 100644 index 00000000..3257759c --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/OrderedTimeCodec.php @@ -0,0 +1,68 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ +namespace Ramsey\Uuid\Codec; + +use InvalidArgumentException; +use Ramsey\Uuid\UuidInterface; + +/** + * OrderedTimeCodec optimizes the bytes to increment UUIDs when time goes by, to improve database INSERTs. + * The string value will be unchanged from StringCodec. Only works for UUID type 1. + */ +class OrderedTimeCodec extends StringCodec +{ + + /** + * Encodes a UuidInterface as an optimized binary representation of a UUID + * + * @param UuidInterface $uuid + * @return string Binary string representation of a UUID + */ + public function encodeBinary(UuidInterface $uuid) + { + $fields = $uuid->getFieldsHex(); + + $optimized = [ + $fields['time_hi_and_version'], + $fields['time_mid'], + $fields['time_low'], + $fields['clock_seq_hi_and_reserved'], + $fields['clock_seq_low'], + $fields['node'], + ]; + + return hex2bin(implode('', $optimized)); + } + + /** + * Decodes an optimized binary representation of a UUID into a UuidInterface object instance + * + * @param string $bytes + * @return UuidInterface + * @throws \InvalidArgumentException if string has not 16 characters + */ + public function decodeBytes($bytes) + { + if (strlen($bytes) !== 16) { + throw new InvalidArgumentException('$bytes string should contain 16 characters.'); + } + + $hex = unpack('H*', $bytes)[1]; + + // Rearrange the fields to their original order + $hex = substr($hex, 8, 4) . substr($hex, 12, 4) . substr($hex, 4, 4) . substr($hex, 0, 4) . substr($hex, 16); + + return $this->decode($hex); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/StringCodec.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/StringCodec.php new file mode 100644 index 00000000..7f352065 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/StringCodec.php @@ -0,0 +1,170 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Codec; + +use InvalidArgumentException; +use Ramsey\Uuid\Builder\UuidBuilderInterface; +use Ramsey\Uuid\Exception\InvalidUuidStringException; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; + +/** + * StringCodec encodes and decodes RFC 4122 UUIDs + * + * @link http://tools.ietf.org/html/rfc4122 + */ +class StringCodec implements CodecInterface +{ + /** + * @var UuidBuilderInterface + */ + private $builder; + + /** + * Constructs a StringCodec for use encoding and decoding UUIDs + * + * @param UuidBuilderInterface $builder The UUID builder to use when encoding UUIDs + */ + public function __construct(UuidBuilderInterface $builder) + { + $this->builder = $builder; + } + + /** + * Encodes a UuidInterface as a string representation of a UUID + * + * @param UuidInterface $uuid + * @return string Hexadecimal string representation of a UUID + */ + public function encode(UuidInterface $uuid) + { + $fields = array_values($uuid->getFieldsHex()); + + return vsprintf( + '%08s-%04s-%04s-%02s%02s-%012s', + $fields + ); + } + + /** + * Encodes a UuidInterface as a binary representation of a UUID + * + * @param UuidInterface $uuid + * @return string Binary string representation of a UUID + */ + public function encodeBinary(UuidInterface $uuid) + { + return hex2bin($uuid->getHex()); + } + + /** + * Decodes a string representation of a UUID into a UuidInterface object instance + * + * @param string $encodedUuid + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + public function decode($encodedUuid) + { + $components = $this->extractComponents($encodedUuid); + $fields = $this->getFields($components); + + return $this->builder->build($this, $fields); + } + + /** + * Decodes a binary representation of a UUID into a UuidInterface object instance + * + * @param string $bytes + * @return UuidInterface + * @throws \InvalidArgumentException if string has not 16 characters + */ + public function decodeBytes($bytes) + { + if (strlen($bytes) !== 16) { + throw new InvalidArgumentException('$bytes string should contain 16 characters.'); + } + + $hexUuid = unpack('H*', $bytes); + + return $this->decode($hexUuid[1]); + } + + /** + * Returns the UUID builder + * + * @return UuidBuilderInterface + */ + protected function getBuilder() + { + return $this->builder; + } + + /** + * Returns an array of UUID components (the UUID exploded on its dashes) + * + * @param string $encodedUuid + * @return array + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + protected function extractComponents($encodedUuid) + { + $nameParsed = str_replace(array( + 'urn:', + 'uuid:', + '{', + '}', + '-' + ), '', $encodedUuid); + + // We have stripped out the dashes and are breaking up the string using + // substr(). In this way, we can accept a full hex value that doesn't + // contain dashes. + $components = array( + substr($nameParsed, 0, 8), + substr($nameParsed, 8, 4), + substr($nameParsed, 12, 4), + substr($nameParsed, 16, 4), + substr($nameParsed, 20) + ); + + $nameParsed = implode('-', $components); + + if (!Uuid::isValid($nameParsed)) { + throw new InvalidUuidStringException('Invalid UUID string: ' . $encodedUuid); + } + + return $components; + } + + /** + * Returns the fields that make up this UUID + * + * @see \Ramsey\Uuid\UuidInterface::getFieldsHex() + * @param array $components + * @return array + */ + protected function getFields(array $components) + { + return array( + 'time_low' => str_pad($components[0], 8, '0', STR_PAD_LEFT), + 'time_mid' => str_pad($components[1], 4, '0', STR_PAD_LEFT), + 'time_hi_and_version' => str_pad($components[2], 4, '0', STR_PAD_LEFT), + 'clock_seq_hi_and_reserved' => str_pad(substr($components[3], 0, 2), 2, '0', STR_PAD_LEFT), + 'clock_seq_low' => str_pad(substr($components[3], 2), 2, '0', STR_PAD_LEFT), + 'node' => str_pad($components[4], 12, '0', STR_PAD_LEFT) + ); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php new file mode 100644 index 00000000..2c4ded89 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php @@ -0,0 +1,107 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ +namespace Ramsey\Uuid\Codec; + +use Ramsey\Uuid\UuidInterface; + +/** + * TimestampLastCombCodec encodes and decodes COMB UUIDs which have the timestamp as the first 48 bits. + * To be used with MySQL, PostgreSQL, Oracle. + */ +class TimestampFirstCombCodec extends StringCodec +{ + /** + * Encodes a UuidInterface as a string representation of a timestamp first COMB UUID + * + * @param UuidInterface $uuid + * + * @return string Hexadecimal string representation of a GUID + */ + public function encode(UuidInterface $uuid) + { + $sixPieceComponents = array_values($uuid->getFieldsHex()); + + $this->swapTimestampAndRandomBits($sixPieceComponents); + + return vsprintf( + '%08s-%04s-%04s-%02s%02s-%012s', + $sixPieceComponents + ); + } + + /** + * Encodes a UuidInterface as a binary representation of timestamp first COMB UUID + * + * @param UuidInterface $uuid + * + * @return string Binary string representation of timestamp first COMB UUID + */ + public function encodeBinary(UuidInterface $uuid) + { + $stringEncoding = $this->encode($uuid); + + return hex2bin(str_replace('-', '', $stringEncoding)); + } + + /** + * Decodes a string representation of timestamp first COMB UUID into a UuidInterface object instance + * + * @param string $encodedUuid + * + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + public function decode($encodedUuid) + { + $fivePieceComponents = $this->extractComponents($encodedUuid); + + $this->swapTimestampAndRandomBits($fivePieceComponents); + + return $this->getBuilder()->build($this, $this->getFields($fivePieceComponents)); + } + + /** + * Decodes a binary representation of timestamp first COMB UUID into a UuidInterface object instance + * + * @param string $bytes + * + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + public function decodeBytes($bytes) + { + return $this->decode(bin2hex($bytes)); + } + + /** + * Swaps the first 48 bits with the last 48 bits + * + * @param array $components An array of UUID components (the UUID exploded on its dashes) + * + * @return void + */ + protected function swapTimestampAndRandomBits(array &$components) + { + $last48Bits = $components[4]; + if (count($components) == 6) { + $last48Bits = $components[5]; + $components[5] = $components[0] . $components[1]; + } else { + $components[4] = $components[0] . $components[1]; + } + + $components[0] = substr($last48Bits, 0, 8); + $components[1] = substr($last48Bits, 8, 4); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/TimestampLastCombCodec.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/TimestampLastCombCodec.php new file mode 100644 index 00000000..0cdd009a --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Codec/TimestampLastCombCodec.php @@ -0,0 +1,23 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ +namespace Ramsey\Uuid\Codec; + +/** + * TimestampLastCombCodec encodes and decodes COMB UUIDs which have the timestamp as the last 48 bits. + * To be used with MSSQL. + */ +class TimestampLastCombCodec extends StringCodec +{ + +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Number/BigNumberConverter.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Number/BigNumberConverter.php new file mode 100644 index 00000000..d2351225 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Number/BigNumberConverter.php @@ -0,0 +1,54 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Converter\Number; + +use Moontoast\Math\BigNumber; +use Ramsey\Uuid\Converter\NumberConverterInterface; + +/** + * BigNumberConverter converts UUIDs from hexadecimal characters into + * moontoast/math `BigNumber` representations of integers and vice versa + */ +class BigNumberConverter implements NumberConverterInterface +{ + /** + * Converts a hexadecimal number into a `Moontoast\Math\BigNumber` representation + * + * @param string $hex The hexadecimal string representation to convert + * @return BigNumber + */ + public function fromHex($hex) + { + $number = BigNumber::convertToBase10($hex, 16); + + return new BigNumber($number); + } + + /** + * Converts an integer or `Moontoast\Math\BigNumber` integer representation + * into a hexadecimal string representation + * + * @param int|string|BigNumber $integer An integer or `Moontoast\Math\BigNumber` + * @return string Hexadecimal string + */ + public function toHex($integer) + { + if (!$integer instanceof BigNumber) { + $integer = new BigNumber($integer); + } + + return BigNumber::convertFromBase10($integer, 16); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php new file mode 100644 index 00000000..96a011c6 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php @@ -0,0 +1,58 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Converter\Number; + +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; +use Ramsey\Uuid\Converter\NumberConverterInterface; + +/** + * DegradedNumberConverter throws `UnsatisfiedDependencyException` exceptions + * if attempting to use number conversion functionality in an environment that + * does not support large integers (i.e. when moontoast/math is not available) + */ +class DegradedNumberConverter implements NumberConverterInterface +{ + /** + * Throws an `UnsatisfiedDependencyException` + * + * @param string $hex The hexadecimal string representation to convert + * @return void + * @throws UnsatisfiedDependencyException + */ + public function fromHex($hex) + { + throw new UnsatisfiedDependencyException( + 'Cannot call ' . __METHOD__ . ' without support for large ' + . 'integers, since integer is an unsigned ' + . '128-bit integer; Moontoast\Math\BigNumber is required.' + ); + } + + /** + * Throws an `UnsatisfiedDependencyException` + * + * @param mixed $integer An integer representation to convert + * @return void + * @throws UnsatisfiedDependencyException + */ + public function toHex($integer) + { + throw new UnsatisfiedDependencyException( + 'Cannot call ' . __METHOD__ . ' without support for large ' + . 'integers, since integer is an unsigned ' + . '128-bit integer; Moontoast\Math\BigNumber is required. ' + ); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/NumberConverterInterface.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/NumberConverterInterface.php new file mode 100644 index 00000000..9505e8c6 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/NumberConverterInterface.php @@ -0,0 +1,46 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Converter; + +/** + * NumberConverterInterface converts UUIDs from hexadecimal characters into + * representations of integers and vice versa + */ +interface NumberConverterInterface +{ + /** + * Converts a hexadecimal number into an integer representation of the number + * + * The integer representation returned may be an object or a string + * representation of the integer, depending on the implementation. + * + * @param string $hex The hexadecimal string representation to convert + * @return mixed + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + */ + public function fromHex($hex); + + /** + * Converts an integer representation into a hexadecimal string representation + * of the number + * + * @param mixed $integer An integer representation to convert; this may be + * a true integer, a string integer, or a object representation that + * this converter can understand + * @return string Hexadecimal string + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + */ + public function toHex($integer); +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php new file mode 100644 index 00000000..d47c8019 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php @@ -0,0 +1,58 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Converter\Time; + +use Moontoast\Math\BigNumber; +use Ramsey\Uuid\Converter\TimeConverterInterface; + +/** + * BigNumberTimeConverter uses the moontoast/math library's `BigNumber` to + * provide facilities for converting parts of time into representations that may + * be used in UUIDs + */ +class BigNumberTimeConverter implements TimeConverterInterface +{ + /** + * Uses the provided seconds and micro-seconds to calculate the time_low, + * time_mid, and time_high fields used by RFC 4122 version 1 UUIDs + * + * @param string $seconds + * @param string $microSeconds + * @return string[] An array containing `low`, `mid`, and `high` keys + * @link http://tools.ietf.org/html/rfc4122#section-4.2.2 + */ + public function calculateTime($seconds, $microSeconds) + { + $uuidTime = new BigNumber('0'); + + $sec = new BigNumber($seconds); + $sec->multiply('10000000'); + + $usec = new BigNumber($microSeconds); + $usec->multiply('10'); + + $uuidTime->add($sec) + ->add($usec) + ->add('122192928000000000'); + + $uuidTimeHex = sprintf('%016s', $uuidTime->convertToBase(16)); + + return array( + 'low' => substr($uuidTimeHex, 8), + 'mid' => substr($uuidTimeHex, 4, 4), + 'hi' => substr($uuidTimeHex, 0, 4), + ); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php new file mode 100644 index 00000000..b94589cd --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php @@ -0,0 +1,42 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Converter\Time; + +use Ramsey\Uuid\Converter\TimeConverterInterface; +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; + +/** + * DegradedTimeConverter throws `UnsatisfiedDependencyException` exceptions + * if attempting to use time conversion functionality in an environment that + * does not support large integers (i.e. when moontoast/math is not available) + */ +class DegradedTimeConverter implements TimeConverterInterface +{ + /** + * Throws an `UnsatisfiedDependencyException` + * + * @param string $seconds + * @param string $microSeconds + * @return void + * @throws UnsatisfiedDependencyException if called on a 32-bit system and `Moontoast\Math\BigNumber` is not present + */ + public function calculateTime($seconds, $microSeconds) + { + throw new UnsatisfiedDependencyException( + 'When calling ' . __METHOD__ . ' on a 32-bit system, ' + . 'Moontoast\Math\BigNumber must be present.' + ); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php new file mode 100644 index 00000000..6a9da74b --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php @@ -0,0 +1,47 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Converter\Time; + +use Ramsey\Uuid\Converter\TimeConverterInterface; + +/** + * PhpTimeConverter uses built-in PHP functions and standard math operations + * available to the PHP programming language to provide facilities for + * converting parts of time into representations that may be used in UUIDs + */ +class PhpTimeConverter implements TimeConverterInterface +{ + /** + * Uses the provided seconds and micro-seconds to calculate the time_low, + * time_mid, and time_high fields used by RFC 4122 version 1 UUIDs + * + * @param string $seconds + * @param string $microSeconds + * @return string[] An array containing `low`, `mid`, and `high` keys + * @link http://tools.ietf.org/html/rfc4122#section-4.2.2 + */ + public function calculateTime($seconds, $microSeconds) + { + // 0x01b21dd213814000 is the number of 100-ns intervals between the + // UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00. + $uuidTime = ($seconds * 10000000) + ($microSeconds * 10) + 0x01b21dd213814000; + + return array( + 'low' => sprintf('%08x', $uuidTime & 0xffffffff), + 'mid' => sprintf('%04x', ($uuidTime >> 32) & 0xffff), + 'hi' => sprintf('%04x', ($uuidTime >> 48) & 0x0fff), + ); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/TimeConverterInterface.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/TimeConverterInterface.php new file mode 100644 index 00000000..382008ac --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Converter/TimeConverterInterface.php @@ -0,0 +1,35 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Converter; + +/** + * TimeConverterInterface provides facilities for converting parts of time into + * representations that may be used in UUIDs + */ +interface TimeConverterInterface +{ + /** + * Uses the provided seconds and micro-seconds to calculate the time_low, + * time_mid, and time_high fields used by RFC 4122 version 1 UUIDs + * + * @param string $seconds + * @param string $microSeconds + * @return string[] An array guaranteed to contain `low`, `mid`, and `high` keys + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if called on a 32-bit system and + * `Moontoast\Math\BigNumber` is not present + * @link http://tools.ietf.org/html/rfc4122#section-4.2.2 + */ + public function calculateTime($seconds, $microSeconds); +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/DegradedUuid.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/DegradedUuid.php new file mode 100644 index 00000000..bcf0be80 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/DegradedUuid.php @@ -0,0 +1,114 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid; + +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; +use Ramsey\Uuid\Exception\UnsupportedOperationException; + +/** + * DegradedUuid represents an RFC 4122 UUID on 32-bit systems + * + * @see Uuid + */ +class DegradedUuid extends Uuid +{ + /** + * @inheritdoc + */ + public function getDateTime() + { + if ($this->getVersion() != 1) { + throw new UnsupportedOperationException('Not a time-based UUID'); + } + + $time = $this->converter->fromHex($this->getTimestampHex()); + + $ts = new \Moontoast\Math\BigNumber($time, 20); + $ts->subtract('122192928000000000'); + $ts->divide('10000000.0'); + $ts->round(); + $unixTime = $ts->getValue(); + + return new \DateTime("@{$unixTime}"); + } + + /** + * For degraded UUIDs, throws an `UnsatisfiedDependencyException` when + * called on a 32-bit system + * + * @throws UnsatisfiedDependencyException if called on a 32-bit system + */ + public function getFields() + { + throw new UnsatisfiedDependencyException( + 'Cannot call ' . __METHOD__ . ' on a 32-bit system, since some ' + . 'values overflow the system max integer value' + . '; consider calling getFieldsHex instead' + ); + } + + /** + * For degraded UUIDs, throws an `UnsatisfiedDependencyException` when + * called on a 32-bit system + * + * @throws UnsatisfiedDependencyException if called on a 32-bit system + */ + public function getNode() + { + throw new UnsatisfiedDependencyException( + 'Cannot call ' . __METHOD__ . ' on a 32-bit system, since node ' + . 'is an unsigned 48-bit integer and can overflow the system ' + . 'max integer value' + . '; consider calling getNodeHex instead' + ); + } + + /** + * For degraded UUIDs, throws an `UnsatisfiedDependencyException` when + * called on a 32-bit system + * + * @throws UnsatisfiedDependencyException if called on a 32-bit system + */ + public function getTimeLow() + { + throw new UnsatisfiedDependencyException( + 'Cannot call ' . __METHOD__ . ' on a 32-bit system, since time_low ' + . 'is an unsigned 32-bit integer and can overflow the system ' + . 'max integer value' + . '; consider calling getTimeLowHex instead' + ); + } + + /** + * For degraded UUIDs, throws an `UnsatisfiedDependencyException` when + * called on a 32-bit system + * + * @throws UnsatisfiedDependencyException if called on a 32-bit system + * @throws UnsupportedOperationException If this UUID is not a version 1 UUID + */ + public function getTimestamp() + { + if ($this->getVersion() != 1) { + throw new UnsupportedOperationException('Not a time-based UUID'); + } + + throw new UnsatisfiedDependencyException( + 'Cannot call ' . __METHOD__ . ' on a 32-bit system, since timestamp ' + . 'is an unsigned 60-bit integer and can overflow the system ' + . 'max integer value' + . '; consider calling getTimestampHex instead' + ); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Exception/InvalidUuidStringException.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Exception/InvalidUuidStringException.php new file mode 100644 index 00000000..0e480649 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Exception/InvalidUuidStringException.php @@ -0,0 +1,22 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Exception; + +/** + * Thrown to indicate that the parsed UUID string is invalid. + */ +class InvalidUuidStringException extends \InvalidArgumentException +{ +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Exception/UnsatisfiedDependencyException.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Exception/UnsatisfiedDependencyException.php new file mode 100644 index 00000000..8b5d5d08 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Exception/UnsatisfiedDependencyException.php @@ -0,0 +1,23 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Exception; + +/** + * Thrown to indicate that the requested operation has dependencies that have not + * been satisfied. + */ +class UnsatisfiedDependencyException extends \RuntimeException +{ +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Exception/UnsupportedOperationException.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Exception/UnsupportedOperationException.php new file mode 100644 index 00000000..b371b682 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Exception/UnsupportedOperationException.php @@ -0,0 +1,22 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Exception; + +/** + * Thrown to indicate that the requested operation is not supported. + */ +class UnsupportedOperationException extends \RuntimeException +{ +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/FeatureSet.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/FeatureSet.php new file mode 100644 index 00000000..56a774ea --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/FeatureSet.php @@ -0,0 +1,333 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid; + +use Ramsey\Uuid\Converter\TimeConverterInterface; +use Ramsey\Uuid\Generator\PeclUuidTimeGenerator; +use Ramsey\Uuid\Provider\Node\FallbackNodeProvider; +use Ramsey\Uuid\Provider\Node\RandomNodeProvider; +use Ramsey\Uuid\Provider\Node\SystemNodeProvider; +use Ramsey\Uuid\Converter\NumberConverterInterface; +use Ramsey\Uuid\Converter\Number\BigNumberConverter; +use Ramsey\Uuid\Converter\Number\DegradedNumberConverter; +use Ramsey\Uuid\Converter\Time\BigNumberTimeConverter; +use Ramsey\Uuid\Converter\Time\DegradedTimeConverter; +use Ramsey\Uuid\Converter\Time\PhpTimeConverter; +use Ramsey\Uuid\Provider\Time\SystemTimeProvider; +use Ramsey\Uuid\Builder\UuidBuilderInterface; +use Ramsey\Uuid\Builder\DefaultUuidBuilder; +use Ramsey\Uuid\Codec\CodecInterface; +use Ramsey\Uuid\Codec\StringCodec; +use Ramsey\Uuid\Codec\GuidStringCodec; +use Ramsey\Uuid\Builder\DegradedUuidBuilder; +use Ramsey\Uuid\Generator\RandomGeneratorFactory; +use Ramsey\Uuid\Generator\RandomGeneratorInterface; +use Ramsey\Uuid\Generator\TimeGeneratorFactory; +use Ramsey\Uuid\Generator\TimeGeneratorInterface; +use Ramsey\Uuid\Provider\TimeProviderInterface; +use Ramsey\Uuid\Provider\NodeProviderInterface; + +/** + * FeatureSet detects and exposes available features in the current environment + * (32- or 64-bit, available dependencies, etc.) + */ +class FeatureSet +{ + /** + * @var bool + */ + private $disableBigNumber = false; + + /** + * @var bool + */ + private $disable64Bit = false; + + /** + * @var bool + */ + private $ignoreSystemNode = false; + + /** + * @var bool + */ + private $enablePecl = false; + + /** + * @var UuidBuilderInterface + */ + private $builder; + + /** + * @var CodecInterface + */ + private $codec; + + /** + * @var NodeProviderInterface + */ + private $nodeProvider; + + /** + * @var NumberConverterInterface + */ + private $numberConverter; + + /** + * @var RandomGeneratorInterface + */ + private $randomGenerator; + + /** + * @var TimeGeneratorInterface + */ + private $timeGenerator; + + /** + * Constructs a `FeatureSet` for use by a `UuidFactory` to determine or set + * features available to the environment + * + * @param bool $useGuids Whether to build UUIDs using the `GuidStringCodec` + * @param bool $force32Bit Whether to force the use of 32-bit functionality + * (primarily for testing purposes) + * @param bool $forceNoBigNumber Whether to disable the use of moontoast/math + * `BigNumber` (primarily for testing purposes) + * @param bool $ignoreSystemNode Whether to disable attempts to check for + * the system host ID (primarily for testing purposes) + * @param bool $enablePecl Whether to enable the use of the `PeclUuidTimeGenerator` + * to generate version 1 UUIDs + */ + public function __construct( + $useGuids = false, + $force32Bit = false, + $forceNoBigNumber = false, + $ignoreSystemNode = false, + $enablePecl = false + ) { + $this->disableBigNumber = $forceNoBigNumber; + $this->disable64Bit = $force32Bit; + $this->ignoreSystemNode = $ignoreSystemNode; + $this->enablePecl = $enablePecl; + + $this->numberConverter = $this->buildNumberConverter(); + $this->builder = $this->buildUuidBuilder(); + $this->codec = $this->buildCodec($useGuids); + $this->nodeProvider = $this->buildNodeProvider(); + $this->randomGenerator = $this->buildRandomGenerator(); + $this->setTimeProvider(new SystemTimeProvider()); + } + + /** + * Returns the builder configured for this environment + * + * @return UuidBuilderInterface + */ + public function getBuilder() + { + return $this->builder; + } + + /** + * Returns the UUID UUID coder-decoder configured for this environment + * + * @return CodecInterface + */ + public function getCodec() + { + return $this->codec; + } + + /** + * Returns the system node ID provider configured for this environment + * + * @return NodeProviderInterface + */ + public function getNodeProvider() + { + return $this->nodeProvider; + } + + /** + * Returns the number converter configured for this environment + * + * @return NumberConverterInterface + */ + public function getNumberConverter() + { + return $this->numberConverter; + } + + /** + * Returns the random UUID generator configured for this environment + * + * @return RandomGeneratorInterface + */ + public function getRandomGenerator() + { + return $this->randomGenerator; + } + + /** + * Returns the time-based UUID generator configured for this environment + * + * @return TimeGeneratorInterface + */ + public function getTimeGenerator() + { + return $this->timeGenerator; + } + + /** + * Sets the time provider for use in this environment + * + * @param TimeProviderInterface $timeProvider + */ + public function setTimeProvider(TimeProviderInterface $timeProvider) + { + $this->timeGenerator = $this->buildTimeGenerator($timeProvider); + } + + /** + * Determines which UUID coder-decoder to use and returns the configured + * codec for this environment + * + * @param bool $useGuids Whether to build UUIDs using the `GuidStringCodec` + * @return CodecInterface + */ + protected function buildCodec($useGuids = false) + { + if ($useGuids) { + return new GuidStringCodec($this->builder); + } + + return new StringCodec($this->builder); + } + + /** + * Determines which system node ID provider to use and returns the configured + * system node ID provider for this environment + * + * @return NodeProviderInterface + */ + protected function buildNodeProvider() + { + if ($this->ignoreSystemNode) { + return new RandomNodeProvider(); + } + + return new FallbackNodeProvider([ + new SystemNodeProvider(), + new RandomNodeProvider() + ]); + } + + /** + * Determines which number converter to use and returns the configured + * number converter for this environment + * + * @return NumberConverterInterface + */ + protected function buildNumberConverter() + { + if ($this->hasBigNumber()) { + return new BigNumberConverter(); + } + + return new DegradedNumberConverter(); + } + + /** + * Determines which random UUID generator to use and returns the configured + * random UUID generator for this environment + * + * @return RandomGeneratorInterface + */ + protected function buildRandomGenerator() + { + return (new RandomGeneratorFactory())->getGenerator(); + } + + /** + * Determines which time-based UUID generator to use and returns the configured + * time-based UUID generator for this environment + * + * @param TimeProviderInterface $timeProvider + * @return TimeGeneratorInterface + */ + protected function buildTimeGenerator(TimeProviderInterface $timeProvider) + { + if ($this->enablePecl) { + return new PeclUuidTimeGenerator(); + } + + return (new TimeGeneratorFactory( + $this->nodeProvider, + $this->buildTimeConverter(), + $timeProvider + ))->getGenerator(); + } + + /** + * Determines which time converter to use and returns the configured + * time converter for this environment + * + * @return TimeConverterInterface + */ + protected function buildTimeConverter() + { + if ($this->is64BitSystem()) { + return new PhpTimeConverter(); + } elseif ($this->hasBigNumber()) { + return new BigNumberTimeConverter(); + } + + return new DegradedTimeConverter(); + } + + /** + * Determines which UUID builder to use and returns the configured UUID + * builder for this environment + * + * @return UuidBuilderInterface + */ + protected function buildUuidBuilder() + { + if ($this->is64BitSystem()) { + return new DefaultUuidBuilder($this->numberConverter); + } + + return new DegradedUuidBuilder($this->numberConverter); + } + + /** + * Returns true if the system has `Moontoast\Math\BigNumber` + * + * @return bool + */ + protected function hasBigNumber() + { + return class_exists('Moontoast\Math\BigNumber') && !$this->disableBigNumber; + } + + /** + * Returns true if the system is 64-bit, false otherwise + * + * @return bool + */ + protected function is64BitSystem() + { + return PHP_INT_SIZE == 8 && !$this->disable64Bit; + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/CombGenerator.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/CombGenerator.php new file mode 100644 index 00000000..7a948231 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/CombGenerator.php @@ -0,0 +1,88 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +use Ramsey\Uuid\Converter\NumberConverterInterface; + +/** + * CombGenerator provides functionality to generate COMB (combined GUID/timestamp) + * sequential UUIDs + * + * @link https://en.wikipedia.org/wiki/Globally_unique_identifier#Sequential_algorithms + */ +class CombGenerator implements RandomGeneratorInterface +{ + const TIMESTAMP_BYTES = 6; + + /** + * @var RandomGeneratorInterface + */ + private $randomGenerator; + + /** + * @var NumberConverterInterface + */ + private $converter; + + /** + * Constructs a `CombGenerator` using a random-number generator and a number converter + * + * @param RandomGeneratorInterface $generator Random-number generator for the non-time part. + * @param NumberConverterInterface $numberConverter Instance of number converter. + */ + public function __construct(RandomGeneratorInterface $generator, NumberConverterInterface $numberConverter) + { + $this->converter = $numberConverter; + $this->randomGenerator = $generator; + } + + /** + * Generates a string of binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + * @throws \InvalidArgumentException if length is not a positive integer + * @throws \Exception + */ + public function generate($length) + { + if ($length < self::TIMESTAMP_BYTES || $length < 0) { + throw new \InvalidArgumentException('Length must be a positive integer.'); + } + + $hash = ''; + + if (self::TIMESTAMP_BYTES > 0 && $length > self::TIMESTAMP_BYTES) { + $hash = $this->randomGenerator->generate($length - self::TIMESTAMP_BYTES); + } + + $lsbTime = str_pad($this->converter->toHex($this->timestamp()), self::TIMESTAMP_BYTES * 2, '0', STR_PAD_LEFT); + + return hex2bin(str_pad(bin2hex($hash), $length - self::TIMESTAMP_BYTES, '0') . $lsbTime); + } + + /** + * Returns current timestamp as integer, precise to 0.00001 seconds + * + * @return string + */ + private function timestamp() + { + $time = explode(' ', microtime(false)); + + return $time[1] . substr($time[0], 2, 5); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/DefaultTimeGenerator.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/DefaultTimeGenerator.php new file mode 100644 index 00000000..c9969b3a --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/DefaultTimeGenerator.php @@ -0,0 +1,138 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +use Ramsey\Uuid\BinaryUtils; +use Ramsey\Uuid\Converter\TimeConverterInterface; +use Ramsey\Uuid\Provider\NodeProviderInterface; +use Ramsey\Uuid\Provider\TimeProviderInterface; + +/** + * DefaultTimeGenerator provides functionality to generate strings of binary + * data for version 1 UUIDs based on a host ID, sequence number, and the current + * time + */ +class DefaultTimeGenerator implements TimeGeneratorInterface +{ + /** + * @var NodeProviderInterface + */ + private $nodeProvider; + + /** + * @var TimeConverterInterface + */ + private $timeConverter; + + /** + * @var TimeProviderInterface + */ + private $timeProvider; + + /** + * Constructs a `DefaultTimeGenerator` using a node provider, time converter, + * and time provider + * + * @param NodeProviderInterface $nodeProvider + * @param TimeConverterInterface $timeConverter + * @param TimeProviderInterface $timeProvider + */ + public function __construct( + NodeProviderInterface $nodeProvider, + TimeConverterInterface $timeConverter, + TimeProviderInterface $timeProvider + ) { + $this->nodeProvider = $nodeProvider; + $this->timeConverter = $timeConverter; + $this->timeProvider = $timeProvider; + } + + /** + * Generate a version 1 UUID from a host ID, sequence number, and the current time + * + * If $node is not given, we will attempt to obtain the local hardware + * address. If $clockSeq is given, it is used as the sequence number; + * otherwise a random 14-bit sequence number is chosen. + * + * @param int|string $node A 48-bit number representing the hardware address + * This number may be represented as an integer or a hexadecimal string. + * @param int $clockSeq A 14-bit number used to help avoid duplicates that + * could arise when the clock is set backwards in time or if the node ID + * changes. + * @return string A binary string + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if called on a 32-bit system and + * `Moontoast\Math\BigNumber` is not present + * @throws \InvalidArgumentException + * @throws \Exception if it was not possible to gather sufficient entropy + */ + public function generate($node = null, $clockSeq = null) + { + $node = $this->getValidNode($node); + + if ($clockSeq === null) { + // Not using "stable storage"; see RFC 4122, Section 4.2.1.1 + $clockSeq = random_int(0, 0x3fff); + } + + // Create a 60-bit time value as a count of 100-nanosecond intervals + // since 00:00:00.00, 15 October 1582 + $timeOfDay = $this->timeProvider->currentTime(); + $uuidTime = $this->timeConverter->calculateTime($timeOfDay['sec'], $timeOfDay['usec']); + + $timeHi = BinaryUtils::applyVersion($uuidTime['hi'], 1); + $clockSeqHi = BinaryUtils::applyVariant($clockSeq >> 8); + + $hex = vsprintf( + '%08s%04s%04s%02s%02s%012s', + array( + $uuidTime['low'], + $uuidTime['mid'], + sprintf('%04x', $timeHi), + sprintf('%02x', $clockSeqHi), + sprintf('%02x', $clockSeq & 0xff), + $node, + ) + ); + + return hex2bin($hex); + } + + /** + * Uses the node provider given when constructing this instance to get + * the node ID (usually a MAC address) + * + * @param string|int $node A node value that may be used to override the node provider + * @return string Hexadecimal representation of the node ID + * @throws \InvalidArgumentException + * @throws \Exception + */ + protected function getValidNode($node) + { + if ($node === null) { + $node = $this->nodeProvider->getNode(); + } + + // Convert the node to hex, if it is still an integer + if (is_int($node)) { + $node = sprintf('%012x', $node); + } + + if (!ctype_xdigit($node) || strlen($node) > 12) { + throw new \InvalidArgumentException('Invalid node value'); + } + + return strtolower(sprintf('%012s', $node)); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/MtRandGenerator.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/MtRandGenerator.php new file mode 100644 index 00000000..f58b7835 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/MtRandGenerator.php @@ -0,0 +1,41 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +/** + * MtRandRandomGenerator provides functionality to generate strings of random + * binary data using the `mt_rand()` PHP function + * + * @link http://php.net/mt_rand + */ +class MtRandGenerator implements RandomGeneratorInterface +{ + /** + * Generates a string of random binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + */ + public function generate($length) + { + $bytes = ''; + + for ($i = 1; $i <= $length; $i++) { + $bytes = chr(mt_rand(0, 255)) . $bytes; + } + + return $bytes; + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/OpenSslGenerator.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/OpenSslGenerator.php new file mode 100644 index 00000000..e8ec6a4d --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/OpenSslGenerator.php @@ -0,0 +1,38 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +/** + * OpenSslRandomGenerator provides functionality to generate strings of random + * binary data using the `openssl_random_pseudo_bytes()` PHP function + * + * The use of this generator requires PHP to be compiled using the + * `--with-openssl` option. + * + * @link http://php.net/openssl_random_pseudo_bytes + */ +class OpenSslGenerator implements RandomGeneratorInterface +{ + /** + * Generates a string of random binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + */ + public function generate($length) + { + return openssl_random_pseudo_bytes($length); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php new file mode 100644 index 00000000..fc2ef7e4 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php @@ -0,0 +1,37 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +/** + * PeclUuidRandomGenerator provides functionality to generate strings of random + * binary data using the PECL UUID PHP extension + * + * @link https://pecl.php.net/package/uuid + */ +class PeclUuidRandomGenerator implements RandomGeneratorInterface +{ + /** + * Generates a string of random binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + */ + public function generate($length) + { + $uuid = uuid_create(UUID_TYPE_RANDOM); + + return uuid_parse($uuid); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php new file mode 100644 index 00000000..7ccf16fd --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php @@ -0,0 +1,38 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +/** + * PeclUuidTimeGenerator provides functionality to generate strings of binary + * data for version 1 UUIDs using the PECL UUID PHP extension + * + * @link https://pecl.php.net/package/uuid + */ +class PeclUuidTimeGenerator implements TimeGeneratorInterface +{ + /** + * Generate a version 1 UUID using the PECL UUID extension + * + * @param int|string $node Not used in this context + * @param int $clockSeq Not used in this context + * @return string A binary string + */ + public function generate($node = null, $clockSeq = null) + { + $uuid = uuid_create(UUID_TYPE_TIME); + + return uuid_parse($uuid); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomBytesGenerator.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomBytesGenerator.php new file mode 100644 index 00000000..aaa285df --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomBytesGenerator.php @@ -0,0 +1,37 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +/** + * RandomBytesGenerator provides functionality to generate strings of random + * binary data using `random_bytes()` function in PHP 7+ or paragonie/random_compat + * + * @link http://php.net/random_bytes + * @link https://github.com/paragonie/random_compat + */ +class RandomBytesGenerator implements RandomGeneratorInterface +{ + /** + * Generates a string of random binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + * @throws \Exception if it was not possible to gather sufficient entropy + */ + public function generate($length) + { + return random_bytes($length); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomGeneratorFactory.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomGeneratorFactory.php new file mode 100644 index 00000000..39110622 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomGeneratorFactory.php @@ -0,0 +1,31 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +/** + * A factory for retrieving a random generator, based on the environment + */ +class RandomGeneratorFactory +{ + /** + * Returns a default random generator, based on the current environment + * + * @return RandomGeneratorInterface + */ + public static function getGenerator() + { + return new RandomBytesGenerator(); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomGeneratorInterface.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomGeneratorInterface.php new file mode 100644 index 00000000..3a1bcae7 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomGeneratorInterface.php @@ -0,0 +1,33 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +/** + * RandomGeneratorInterface provides functionality to generate strings of random + * binary data + */ +interface RandomGeneratorInterface +{ + /** + * Generates a string of random binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + * @throws \InvalidArgumentException + * @throws \Exception if it was not possible to gather sufficient entropy + */ + public function generate($length); +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomLibAdapter.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomLibAdapter.php new file mode 100644 index 00000000..25b54a83 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/RandomLibAdapter.php @@ -0,0 +1,62 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +use RandomLib\Generator; +use RandomLib\Factory; + +/** + * RandomLibAdapter provides functionality to generate strings of random + * binary data using the ircmaxell/random-lib library + * + * @link https://packagist.org/packages/ircmaxell/random-lib + */ +class RandomLibAdapter implements RandomGeneratorInterface +{ + /** + * @var Generator + */ + private $generator; + + /** + * Constructs a `RandomLibAdapter` using a `RandomLib\Generator` + * + * By default, if no `Generator` is passed in, this creates a medium-strength + * generator to use when generating random binary data. + * + * @param Generator $generator An ircmaxell/random-lib `Generator` + */ + public function __construct(Generator $generator = null) + { + $this->generator = $generator; + + if ($this->generator === null) { + $factory = new Factory(); + + $this->generator = $factory->getMediumStrengthGenerator(); + } + } + + /** + * Generates a string of random binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + */ + public function generate($length) + { + return $this->generator->generate($length); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/SodiumRandomGenerator.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/SodiumRandomGenerator.php new file mode 100644 index 00000000..6b08f540 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/SodiumRandomGenerator.php @@ -0,0 +1,36 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +/** + * SodiumRandomGenerator provides functionality to generate strings of random + * binary data using the PECL libsodium extension + * + * @link http://pecl.php.net/package/libsodium + * @link https://paragonie.com/book/pecl-libsodium + */ +class SodiumRandomGenerator implements RandomGeneratorInterface +{ + /** + * Generates a string of random binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + */ + public function generate($length) + { + return \Sodium\randombytes_buf($length); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/TimeGeneratorFactory.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/TimeGeneratorFactory.php new file mode 100644 index 00000000..24d501bb --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/TimeGeneratorFactory.php @@ -0,0 +1,72 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +use Ramsey\Uuid\Converter\TimeConverterInterface; +use Ramsey\Uuid\Provider\NodeProviderInterface; +use Ramsey\Uuid\Provider\TimeProviderInterface; + +/** + * A factory for retrieving a time generator, based on the environment + */ +class TimeGeneratorFactory +{ + /** + * @var NodeProviderInterface + */ + private $nodeProvider; + + /** + * @var TimeConverterInterface + */ + private $timeConverter; + + /** + * @var TimeProviderInterface + */ + private $timeProvider; + + /** + * Constructs a `TimeGeneratorFactory` using a node provider, time converter, + * and time provider + * + * @param NodeProviderInterface $nodeProvider + * @param TimeConverterInterface $timeConverter + * @param TimeProviderInterface $timeProvider + */ + public function __construct( + NodeProviderInterface $nodeProvider, + TimeConverterInterface $timeConverter, + TimeProviderInterface $timeProvider + ) { + $this->nodeProvider = $nodeProvider; + $this->timeConverter = $timeConverter; + $this->timeProvider = $timeProvider; + } + + /** + * Returns a default time generator, based on the current environment + * + * @return TimeGeneratorInterface + */ + public function getGenerator() + { + return new DefaultTimeGenerator( + $this->nodeProvider, + $this->timeConverter, + $this->timeProvider + ); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/TimeGeneratorInterface.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/TimeGeneratorInterface.php new file mode 100644 index 00000000..cb182ea0 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Generator/TimeGeneratorInterface.php @@ -0,0 +1,39 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +/** + * TimeGeneratorInterface provides functionality to generate strings of binary + * data for version 1 UUIDs based on a host ID, sequence number, and the current + * time + */ +interface TimeGeneratorInterface +{ + /** + * Generate a version 1 UUID from a host ID, sequence number, and the current time + * + * @param int|string $node A 48-bit number representing the hardware address + * This number may be represented as an integer or a hexadecimal string. + * @param int $clockSeq A 14-bit number used to help avoid duplicates that + * could arise when the clock is set backwards in time or if the node ID + * changes. + * @return string A binary string + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if called on a 32-bit system and + * `Moontoast\Math\BigNumber` is not present + * @throws \InvalidArgumentException + * @throws \Exception if it was not possible to gather sufficient entropy + */ + public function generate($node = null, $clockSeq = null); +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php new file mode 100644 index 00000000..289fddea --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php @@ -0,0 +1,58 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Provider\Node; + +use Ramsey\Uuid\Provider\NodeProviderInterface; + +/** + * FallbackNodeProvider attempts to gain the system host ID from an array of + * providers, falling back to the next in line in the event a host ID can not be + * obtained + */ +class FallbackNodeProvider implements NodeProviderInterface +{ + /** + * @var NodeProviderInterface[] + */ + private $nodeProviders; + + /** + * Constructs a `FallbackNodeProvider` using an array of node providers + * + * @param NodeProviderInterface[] $providers Array of node providers + */ + public function __construct(array $providers) + { + $this->nodeProviders = $providers; + } + + /** + * Returns the system node ID by iterating over an array of node providers + * and returning the first non-empty value found + * + * @return string System node ID as a hexadecimal string + * @throws \Exception + */ + public function getNode() + { + foreach ($this->nodeProviders as $provider) { + if ($node = $provider->getNode()) { + return $node; + } + } + + return null; + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php new file mode 100644 index 00000000..76c570d7 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php @@ -0,0 +1,42 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Provider\Node; + +use Ramsey\Uuid\Provider\NodeProviderInterface; + +/** + * RandomNodeProvider provides functionality to generate a random node ID, in + * the event that the node ID could not be obtained from the host system + * + * @link http://tools.ietf.org/html/rfc4122#section-4.5 + */ +class RandomNodeProvider implements NodeProviderInterface +{ + /** + * Returns the system node ID + * + * @return string System node ID as a hexadecimal string + * @throws \Exception if it was not possible to gather sufficient entropy + */ + public function getNode() + { + $node = hexdec(bin2hex(random_bytes(6))); + + // Set the multicast bit; see RFC 4122, section 4.5. + $node = $node | 0x010000000000; + + return str_pad(dechex($node), 12, '0', STR_PAD_LEFT); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php new file mode 100644 index 00000000..ae6a09ea --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php @@ -0,0 +1,125 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Provider\Node; + +use Ramsey\Uuid\Provider\NodeProviderInterface; + +/** + * SystemNodeProvider provides functionality to get the system node ID (MAC + * address) using external system calls + */ +class SystemNodeProvider implements NodeProviderInterface +{ + /** + * Returns the system node ID + * + * @return string|false System node ID as a hexadecimal string, or false if it is not found + */ + public function getNode() + { + static $node = null; + + if ($node !== null) { + return $node; + } + + $pattern = '/[^:]([0-9A-Fa-f]{2}([:-])[0-9A-Fa-f]{2}(\2[0-9A-Fa-f]{2}){4})[^:]/'; + $matches = array(); + + // first try a linux specific way + $node = $this->getSysfs(); + + // Search the ifconfig output for all MAC addresses and return + // the first one found + if ($node === false) { + if (preg_match_all($pattern, $this->getIfconfig(), $matches, PREG_PATTERN_ORDER)) { + $node = $matches[1][0]; + } + } + if ($node !== false) { + $node = str_replace([':', '-'], '', $node); + } + return $node; + } + + /** + * Returns the network interface configuration for the system + * + * @codeCoverageIgnore + * @return string + */ + protected function getIfconfig() + { + if (strpos(strtolower(ini_get('disable_functions')), 'passthru') !== false) { + return ''; + } + + ob_start(); + switch (strtoupper(substr(php_uname('a'), 0, 3))) { + case 'WIN': + passthru('ipconfig /all 2>&1'); + break; + case 'DAR': + passthru('ifconfig 2>&1'); + break; + case 'FRE': + passthru('netstat -i -f link 2>&1'); + break; + case 'LIN': + default: + passthru('netstat -ie 2>&1'); + break; + } + + return ob_get_clean(); + } + + /** + * Returns mac address from the first system interface via the sysfs interface + * + * @return string|bool + */ + protected function getSysfs() + { + $mac = false; + + if (strtoupper(php_uname('s')) === 'LINUX') { + $addressPaths = glob('/sys/class/net/*/address', GLOB_NOSORT); + + if (empty($addressPaths)) { + return false; + } + + array_walk($addressPaths, function ($addressPath) use (&$macs) { + $macs[] = file_get_contents($addressPath); + }); + + $macs = array_map('trim', $macs); + + // remove invalid entries + $macs = array_filter($macs, function ($mac) { + return + // localhost adapter + $mac !== '00:00:00:00:00:00' && + // must match mac adress + preg_match('/^([0-9a-f]{2}:){5}[0-9a-f]{2}$/i', $mac); + }); + + $mac = reset($macs); + } + + return $mac; + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/NodeProviderInterface.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/NodeProviderInterface.php new file mode 100644 index 00000000..14f747be --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/NodeProviderInterface.php @@ -0,0 +1,30 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Provider; + +/** + * NodeProviderInterface provides functionality to get the node ID (or host ID + * in the form of the system's MAC address) from a specific type of node provider + */ +interface NodeProviderInterface +{ + /** + * Returns the system node ID + * + * @return string System node ID as a hexadecimal string + * @throws \Exception if it was not possible to gather sufficient entropy + */ + public function getNode(); +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php new file mode 100644 index 00000000..a62d39c6 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php @@ -0,0 +1,76 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Provider\Time; + +use Ramsey\Uuid\Provider\TimeProviderInterface; + +/** + * FixedTimeProvider uses an previously-generated timestamp to provide the time + * + * This provider allows the use of a previously-generated timestamp, such as one + * stored in a database, when creating version 1 UUIDs. + */ +class FixedTimeProvider implements TimeProviderInterface +{ + /** + * @var int[] Array containing `sec` and `usec` components of a timestamp + */ + private $fixedTime; + + /** + * Constructs a `FixedTimeProvider` using the provided `$timestamp` + * + * @param int[] Array containing `sec` and `usec` components of a timestamp + * @throws \InvalidArgumentException if the `$timestamp` does not contain `sec` or `usec` components + */ + public function __construct(array $timestamp) + { + if (!array_key_exists('sec', $timestamp) || !array_key_exists('usec', $timestamp)) { + throw new \InvalidArgumentException('Array must contain sec and usec keys.'); + } + + $this->fixedTime = $timestamp; + } + + /** + * Sets the `usec` component of the timestamp + * + * @param int $value The `usec` value to set + */ + public function setUsec($value) + { + $this->fixedTime['usec'] = $value; + } + + /** + * Sets the `sec` component of the timestamp + * + * @param int $value The `sec` value to set + */ + public function setSec($value) + { + $this->fixedTime['sec'] = $value; + } + + /** + * Returns a timestamp array + * + * @return int[] Array containing `sec` and `usec` components of a timestamp + */ + public function currentTime() + { + return $this->fixedTime; + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php new file mode 100644 index 00000000..6442985f --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php @@ -0,0 +1,33 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Provider\Time; + +use Ramsey\Uuid\Provider\TimeProviderInterface; + +/** + * SystemTimeProvider uses built-in PHP functions to provide the time + */ +class SystemTimeProvider implements TimeProviderInterface +{ + /** + * Returns a timestamp array + * + * @return int[] Array containing `sec` and `usec` components of a timestamp + */ + public function currentTime() + { + return gettimeofday(); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/TimeProviderInterface.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/TimeProviderInterface.php new file mode 100644 index 00000000..ef8099dd --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Provider/TimeProviderInterface.php @@ -0,0 +1,29 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Provider; + +/** + * TimeProviderInterface provides functionality to get the time from a specific + * type of time provider + */ +interface TimeProviderInterface +{ + /** + * Returns a timestamp array + * + * @return int[] Array guaranteed to contain `sec` and `usec` components of a timestamp + */ + public function currentTime(); +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/Uuid.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/Uuid.php new file mode 100644 index 00000000..45f9fa44 --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/Uuid.php @@ -0,0 +1,740 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid; + +use Ramsey\Uuid\Converter\NumberConverterInterface; +use Ramsey\Uuid\Codec\CodecInterface; +use Ramsey\Uuid\Exception\UnsupportedOperationException; + +/** + * Represents a universally unique identifier (UUID), according to RFC 4122. + * + * This class provides immutable UUID objects (the Uuid class) and the static + * methods `uuid1()`, `uuid3()`, `uuid4()`, and `uuid5()` for generating version + * 1, 3, 4, and 5 UUIDs as specified in RFC 4122. + * + * If all you want is a unique ID, you should probably call `uuid1()` or `uuid4()`. + * Note that `uuid1()` may compromise privacy since it creates a UUID containing + * the computer’s network address. `uuid4()` creates a random UUID. + * + * @link http://tools.ietf.org/html/rfc4122 + * @link http://en.wikipedia.org/wiki/Universally_unique_identifier + * @link http://docs.python.org/3/library/uuid.html + * @link http://docs.oracle.com/javase/6/docs/api/java/util/UUID.html + */ +class Uuid implements UuidInterface +{ + /** + * When this namespace is specified, the name string is a fully-qualified domain name. + * @link http://tools.ietf.org/html/rfc4122#appendix-C + */ + const NAMESPACE_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; + + /** + * When this namespace is specified, the name string is a URL. + * @link http://tools.ietf.org/html/rfc4122#appendix-C + */ + const NAMESPACE_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; + + /** + * When this namespace is specified, the name string is an ISO OID. + * @link http://tools.ietf.org/html/rfc4122#appendix-C + */ + const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8'; + + /** + * When this namespace is specified, the name string is an X.500 DN in DER or a text output format. + * @link http://tools.ietf.org/html/rfc4122#appendix-C + */ + const NAMESPACE_X500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8'; + + /** + * The nil UUID is special form of UUID that is specified to have all 128 bits set to zero. + * @link http://tools.ietf.org/html/rfc4122#section-4.1.7 + */ + const NIL = '00000000-0000-0000-0000-000000000000'; + + /** + * Reserved for NCS compatibility. + * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 + */ + const RESERVED_NCS = 0; + + /** + * Specifies the UUID layout given in RFC 4122. + * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 + */ + const RFC_4122 = 2; + + /** + * Reserved for Microsoft compatibility. + * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 + */ + const RESERVED_MICROSOFT = 6; + + /** + * Reserved for future definition. + * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 + */ + const RESERVED_FUTURE = 7; + + /** + * Regular expression pattern for matching a valid UUID of any variant. + */ + const VALID_PATTERN = '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'; + + /** + * Version 1 (time-based) UUID object constant identifier + */ + const UUID_TYPE_TIME = 1; + + /** + * Version 2 (identifier-based) UUID object constant identifier + */ + const UUID_TYPE_IDENTIFIER = 2; + + /** + * Version 3 (name-based and hashed with MD5) UUID object constant identifier + */ + const UUID_TYPE_HASH_MD5 = 3; + + /** + * Version 4 (random) UUID object constant identifier + */ + const UUID_TYPE_RANDOM = 4; + + /** + * Version 5 (name-based and hashed with SHA1) UUID object constant identifier + */ + const UUID_TYPE_HASH_SHA1 = 5; + + /** + * The factory to use when creating UUIDs. + * @var UuidFactoryInterface + */ + private static $factory = null; + + /** + * The codec to use when encoding or decoding UUID strings. + * @var CodecInterface + */ + protected $codec; + + /** + * The fields that make up this UUID. + * + * This is initialized to the nil value. + * + * @var array + * @see UuidInterface::getFieldsHex() + */ + protected $fields = array( + 'time_low' => '00000000', + 'time_mid' => '0000', + 'time_hi_and_version' => '0000', + 'clock_seq_hi_and_reserved' => '00', + 'clock_seq_low' => '00', + 'node' => '000000000000', + ); + + /** + * The number converter to use for converting hex values to/from integers. + * @var NumberConverterInterface + */ + protected $converter; + + /** + * Creates a universally unique identifier (UUID) from an array of fields. + * + * Unless you're making advanced use of this library to generate identifiers + * that deviate from RFC 4122, you probably do not want to instantiate a + * UUID directly. Use the static methods, instead: + * + * ``` + * use Ramsey\Uuid\Uuid; + * + * $timeBasedUuid = Uuid::uuid1(); + * $namespaceMd5Uuid = Uuid::uuid3(Uuid::NAMESPACE_URL, 'http://php.net/'); + * $randomUuid = Uuid::uuid4(); + * $namespaceSha1Uuid = Uuid::uuid5(Uuid::NAMESPACE_URL, 'http://php.net/'); + * ``` + * + * @param array $fields An array of fields from which to construct a UUID; + * see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure. + * @param NumberConverterInterface $converter The number converter to use + * for converting hex values to/from integers. + * @param CodecInterface $codec The codec to use when encoding or decoding + * UUID strings. + */ + public function __construct( + array $fields, + NumberConverterInterface $converter, + CodecInterface $codec + ) { + $this->fields = $fields; + $this->codec = $codec; + $this->converter = $converter; + } + + /** + * Converts this UUID object to a string when the object is used in any + * string context. + * + * @return string + * @link http://www.php.net/manual/en/language.oop5.magic.php#object.tostring + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Converts this UUID object to a string when the object is serialized + * with `json_encode()` + * + * @return string + * @link http://php.net/manual/en/class.jsonserializable.php + */ + public function jsonSerialize() + { + return $this->toString(); + } + + /** + * Converts this UUID object to a string when the object is serialized + * with `serialize()` + * + * @return string + * @link http://php.net/manual/en/class.serializable.php + */ + public function serialize() + { + return $this->toString(); + } + + /** + * Re-constructs the object from its serialized form. + * + * @param string $serialized + * @link http://php.net/manual/en/class.serializable.php + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + public function unserialize($serialized) + { + $uuid = self::fromString($serialized); + $this->codec = $uuid->codec; + $this->converter = $uuid->converter; + $this->fields = $uuid->fields; + } + + public function compareTo(UuidInterface $other) + { + $comparison = 0; + + if ($this->getMostSignificantBitsHex() < $other->getMostSignificantBitsHex()) { + $comparison = -1; + } elseif ($this->getMostSignificantBitsHex() > $other->getMostSignificantBitsHex()) { + $comparison = 1; + } elseif ($this->getLeastSignificantBitsHex() < $other->getLeastSignificantBitsHex()) { + $comparison = -1; + } elseif ($this->getLeastSignificantBitsHex() > $other->getLeastSignificantBitsHex()) { + $comparison = 1; + } + + return $comparison; + } + + public function equals($other) + { + if (!($other instanceof UuidInterface)) { + return false; + } + + return ($this->compareTo($other) == 0); + } + + public function getBytes() + { + return $this->codec->encodeBinary($this); + } + + /** + * Returns the high field of the clock sequence multiplexed with the variant + * (bits 65-72 of the UUID). + * + * @return int Unsigned 8-bit integer value of clock_seq_hi_and_reserved + */ + public function getClockSeqHiAndReserved() + { + return hexdec($this->getClockSeqHiAndReservedHex()); + } + + public function getClockSeqHiAndReservedHex() + { + return $this->fields['clock_seq_hi_and_reserved']; + } + + /** + * Returns the low field of the clock sequence (bits 73-80 of the UUID). + * + * @return int Unsigned 8-bit integer value of clock_seq_low + */ + public function getClockSeqLow() + { + return hexdec($this->getClockSeqLowHex()); + } + + public function getClockSeqLowHex() + { + return $this->fields['clock_seq_low']; + } + + /** + * Returns the clock sequence value associated with this UUID. + * + * For UUID version 1, the clock sequence is used to help avoid + * duplicates that could arise when the clock is set backwards in time + * or if the node ID changes. + * + * For UUID version 3 or 5, the clock sequence is a 14-bit value + * constructed from a name as described in RFC 4122, Section 4.3. + * + * For UUID version 4, clock sequence is a randomly or pseudo-randomly + * generated 14-bit value as described in RFC 4122, Section 4.4. + * + * @return int Unsigned 14-bit integer value of clock sequence + * @link http://tools.ietf.org/html/rfc4122#section-4.1.5 + */ + public function getClockSequence() + { + return (($this->getClockSeqHiAndReserved() & 0x3f) << 8) + | $this->getClockSeqLow(); + } + + public function getClockSequenceHex() + { + return sprintf('%04x', $this->getClockSequence()); + } + + public function getNumberConverter() + { + return $this->converter; + } + + /** + * @inheritdoc + */ + public function getDateTime() + { + if ($this->getVersion() != 1) { + throw new UnsupportedOperationException('Not a time-based UUID'); + } + + $unixTime = ($this->getTimestamp() - 0x01b21dd213814000) / 1e7; + $unixTime = number_format($unixTime, 0, '', ''); + + return new \DateTime("@{$unixTime}"); + } + + /** + * Returns an array of the fields of this UUID, with keys named according + * to the RFC 4122 names for the fields. + * + * * **time_low**: The low field of the timestamp, an unsigned 32-bit integer + * * **time_mid**: The middle field of the timestamp, an unsigned 16-bit integer + * * **time_hi_and_version**: The high field of the timestamp multiplexed with + * the version number, an unsigned 16-bit integer + * * **clock_seq_hi_and_reserved**: The high field of the clock sequence + * multiplexed with the variant, an unsigned 8-bit integer + * * **clock_seq_low**: The low field of the clock sequence, an unsigned + * 8-bit integer + * * **node**: The spatially unique node identifier, an unsigned 48-bit + * integer + * + * @return array The UUID fields represented as integer values + * @link http://tools.ietf.org/html/rfc4122#section-4.1.2 + */ + public function getFields() + { + return array( + 'time_low' => $this->getTimeLow(), + 'time_mid' => $this->getTimeMid(), + 'time_hi_and_version' => $this->getTimeHiAndVersion(), + 'clock_seq_hi_and_reserved' => $this->getClockSeqHiAndReserved(), + 'clock_seq_low' => $this->getClockSeqLow(), + 'node' => $this->getNode(), + ); + } + + public function getFieldsHex() + { + return $this->fields; + } + + public function getHex() + { + return str_replace('-', '', $this->toString()); + } + + /** + * @inheritdoc + */ + public function getInteger() + { + return $this->converter->fromHex($this->getHex()); + } + + /** + * Returns the least significant 64 bits of this UUID's 128 bit value. + * + * @return mixed Converted representation of the unsigned 64-bit integer value + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + */ + public function getLeastSignificantBits() + { + return $this->converter->fromHex($this->getLeastSignificantBitsHex()); + } + + public function getLeastSignificantBitsHex() + { + return sprintf( + '%02s%02s%012s', + $this->fields['clock_seq_hi_and_reserved'], + $this->fields['clock_seq_low'], + $this->fields['node'] + ); + } + + /** + * Returns the most significant 64 bits of this UUID's 128 bit value. + * + * @return mixed Converted representation of the unsigned 64-bit integer value + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + */ + public function getMostSignificantBits() + { + return $this->converter->fromHex($this->getMostSignificantBitsHex()); + } + + public function getMostSignificantBitsHex() + { + return sprintf( + '%08s%04s%04s', + $this->fields['time_low'], + $this->fields['time_mid'], + $this->fields['time_hi_and_version'] + ); + } + + /** + * Returns the node value associated with this UUID + * + * For UUID version 1, the node field consists of an IEEE 802 MAC + * address, usually the host address. For systems with multiple IEEE + * 802 addresses, any available one can be used. The lowest addressed + * octet (octet number 10) contains the global/local bit and the + * unicast/multicast bit, and is the first octet of the address + * transmitted on an 802.3 LAN. + * + * For systems with no IEEE address, a randomly or pseudo-randomly + * generated value may be used; see RFC 4122, Section 4.5. The + * multicast bit must be set in such addresses, in order that they + * will never conflict with addresses obtained from network cards. + * + * For UUID version 3 or 5, the node field is a 48-bit value constructed + * from a name as described in RFC 4122, Section 4.3. + * + * For UUID version 4, the node field is a randomly or pseudo-randomly + * generated 48-bit value as described in RFC 4122, Section 4.4. + * + * @return int Unsigned 48-bit integer value of node + * @link http://tools.ietf.org/html/rfc4122#section-4.1.6 + */ + public function getNode() + { + return hexdec($this->getNodeHex()); + } + + public function getNodeHex() + { + return $this->fields['node']; + } + + /** + * Returns the high field of the timestamp multiplexed with the version + * number (bits 49-64 of the UUID). + * + * @return int Unsigned 16-bit integer value of time_hi_and_version + */ + public function getTimeHiAndVersion() + { + return hexdec($this->getTimeHiAndVersionHex()); + } + + public function getTimeHiAndVersionHex() + { + return $this->fields['time_hi_and_version']; + } + + /** + * Returns the low field of the timestamp (the first 32 bits of the UUID). + * + * @return int Unsigned 32-bit integer value of time_low + */ + public function getTimeLow() + { + return hexdec($this->getTimeLowHex()); + } + + public function getTimeLowHex() + { + return $this->fields['time_low']; + } + + /** + * Returns the middle field of the timestamp (bits 33-48 of the UUID). + * + * @return int Unsigned 16-bit integer value of time_mid + */ + public function getTimeMid() + { + return hexdec($this->getTimeMidHex()); + } + + public function getTimeMidHex() + { + return $this->fields['time_mid']; + } + + /** + * Returns the timestamp value associated with this UUID. + * + * The 60 bit timestamp value is constructed from the time_low, + * time_mid, and time_hi fields of this UUID. The resulting + * timestamp is measured in 100-nanosecond units since midnight, + * October 15, 1582 UTC. + * + * The timestamp value is only meaningful in a time-based UUID, which + * has version type 1. If this UUID is not a time-based UUID then + * this method throws UnsupportedOperationException. + * + * @return int Unsigned 60-bit integer value of the timestamp + * @throws UnsupportedOperationException If this UUID is not a version 1 UUID + * @link http://tools.ietf.org/html/rfc4122#section-4.1.4 + */ + public function getTimestamp() + { + if ($this->getVersion() != 1) { + throw new UnsupportedOperationException('Not a time-based UUID'); + } + + return hexdec($this->getTimestampHex()); + } + + /** + * @inheritdoc + */ + public function getTimestampHex() + { + if ($this->getVersion() != 1) { + throw new UnsupportedOperationException('Not a time-based UUID'); + } + + return sprintf( + '%03x%04s%08s', + ($this->getTimeHiAndVersion() & 0x0fff), + $this->fields['time_mid'], + $this->fields['time_low'] + ); + } + + public function getUrn() + { + return 'urn:uuid:' . $this->toString(); + } + + public function getVariant() + { + $clockSeq = $this->getClockSeqHiAndReserved(); + if (0 === ($clockSeq & 0x80)) { + $variant = self::RESERVED_NCS; + } elseif (0 === ($clockSeq & 0x40)) { + $variant = self::RFC_4122; + } elseif (0 === ($clockSeq & 0x20)) { + $variant = self::RESERVED_MICROSOFT; + } else { + $variant = self::RESERVED_FUTURE; + } + + return $variant; + } + + public function getVersion() + { + if ($this->getVariant() == self::RFC_4122) { + return (int) (($this->getTimeHiAndVersion() >> 12) & 0x0f); + } + + return null; + } + + public function toString() + { + return $this->codec->encode($this); + } + + /** + * Returns the currently set factory used to create UUIDs. + * + * @return UuidFactoryInterface + */ + public static function getFactory() + { + if (!self::$factory) { + self::$factory = new UuidFactory(); + } + + return self::$factory; + } + + /** + * Sets the factory used to create UUIDs. + * + * @param UuidFactoryInterface $factory + */ + public static function setFactory(UuidFactoryInterface $factory) + { + self::$factory = $factory; + } + + /** + * Creates a UUID from a byte string. + * + * @param string $bytes + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + * @throws \InvalidArgumentException + */ + public static function fromBytes($bytes) + { + return self::getFactory()->fromBytes($bytes); + } + + /** + * Creates a UUID from the string standard representation. + * + * @param string $name A string that specifies a UUID + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + public static function fromString($name) + { + return self::getFactory()->fromString($name); + } + + /** + * Creates a UUID from a 128-bit integer string. + * + * @param string $integer String representation of 128-bit integer + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + public static function fromInteger($integer) + { + return self::getFactory()->fromInteger($integer); + } + + /** + * Check if a string is a valid UUID. + * + * @param string $uuid The string UUID to test + * @return boolean + */ + public static function isValid($uuid) + { + $uuid = str_replace(array('urn:', 'uuid:', '{', '}'), '', $uuid); + + if ($uuid == self::NIL) { + return true; + } + + if (!preg_match('/' . self::VALID_PATTERN . '/D', $uuid)) { + return false; + } + + return true; + } + + /** + * Generate a version 1 UUID from a host ID, sequence number, and the current time. + * + * @param int|string $node A 48-bit number representing the hardware address + * This number may be represented as an integer or a hexadecimal string. + * @param int $clockSeq A 14-bit number used to help avoid duplicates that + * could arise when the clock is set backwards in time or if the node ID + * changes. + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if called on a 32-bit system and + * `Moontoast\Math\BigNumber` is not present + * @throws \InvalidArgumentException + * @throws \Exception if it was not possible to gather sufficient entropy + */ + public static function uuid1($node = null, $clockSeq = null) + { + return self::getFactory()->uuid1($node, $clockSeq); + } + + /** + * Generate a version 3 UUID based on the MD5 hash of a namespace identifier + * (which is a UUID) and a name (which is a string). + * + * @param string $ns The UUID namespace in which to create the named UUID + * @param string $name The name to create a UUID for + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + public static function uuid3($ns, $name) + { + return self::getFactory()->uuid3($ns, $name); + } + + /** + * Generate a version 4 (random) UUID. + * + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + * @throws \InvalidArgumentException + * @throws \Exception + */ + public static function uuid4() + { + return self::getFactory()->uuid4(); + } + + /** + * Generate a version 5 UUID based on the SHA-1 hash of a namespace + * identifier (which is a UUID) and a name (which is a string). + * + * @param string $ns The UUID namespace in which to create the named UUID + * @param string $name The name to create a UUID for + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + public static function uuid5($ns, $name) + { + return self::getFactory()->uuid5($ns, $name); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/UuidFactory.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/UuidFactory.php new file mode 100644 index 00000000..99644d4b --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/UuidFactory.php @@ -0,0 +1,314 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid; + +use Ramsey\Uuid\Converter\NumberConverterInterface; +use Ramsey\Uuid\Provider\NodeProviderInterface; +use Ramsey\Uuid\Generator\RandomGeneratorInterface; +use Ramsey\Uuid\Generator\TimeGeneratorInterface; +use Ramsey\Uuid\Codec\CodecInterface; +use Ramsey\Uuid\Builder\UuidBuilderInterface; + +class UuidFactory implements UuidFactoryInterface +{ + /** + * @var CodecInterface + */ + private $codec = null; + + /** + * @var NodeProviderInterface + */ + private $nodeProvider = null; + + /** + * @var NumberConverterInterface + */ + private $numberConverter = null; + + /** + * @var RandomGeneratorInterface + */ + private $randomGenerator = null; + + /** + * @var TimeGeneratorInterface + */ + private $timeGenerator = null; + + /** + * @var UuidBuilderInterface + */ + private $uuidBuilder = null; + + /** + * Constructs a `UuidFactory` for creating `Ramsey\Uuid\UuidInterface` instances + * + * @param FeatureSet $features A set of features for use when creating UUIDs + */ + public function __construct(FeatureSet $features = null) + { + $features = $features ?: new FeatureSet(); + + $this->codec = $features->getCodec(); + $this->nodeProvider = $features->getNodeProvider(); + $this->numberConverter = $features->getNumberConverter(); + $this->randomGenerator = $features->getRandomGenerator(); + $this->timeGenerator = $features->getTimeGenerator(); + $this->uuidBuilder = $features->getBuilder(); + } + + /** + * Returns the UUID coder-decoder used by this factory + * + * @return CodecInterface + */ + public function getCodec() + { + return $this->codec; + } + + /** + * Sets the UUID coder-decoder used by this factory + * + * @param CodecInterface $codec + */ + public function setCodec(CodecInterface $codec) + { + $this->codec = $codec; + } + + /** + * Returns the system node ID provider used by this factory + * + * @return NodeProviderInterface + */ + public function getNodeProvider() + { + return $this->nodeProvider; + } + + /** + * Returns the random UUID generator used by this factory + * + * @return RandomGeneratorInterface + */ + public function getRandomGenerator() + { + return $this->randomGenerator; + } + + /** + * Returns the time-based UUID generator used by this factory + * + * @return TimeGeneratorInterface + */ + public function getTimeGenerator() + { + return $this->timeGenerator; + } + + /** + * Sets the time-based UUID generator this factory will use to generate version 1 UUIDs + * + * @param TimeGeneratorInterface $generator + */ + public function setTimeGenerator(TimeGeneratorInterface $generator) + { + $this->timeGenerator = $generator; + } + + /** + * Returns the number converter used by this factory + * + * @return NumberConverterInterface + */ + public function getNumberConverter() + { + return $this->numberConverter; + } + + /** + * Sets the random UUID generator this factory will use to generate version 4 UUIDs + * + * @param RandomGeneratorInterface $generator + */ + public function setRandomGenerator(RandomGeneratorInterface $generator) + { + $this->randomGenerator = $generator; + } + + /** + * Sets the number converter this factory will use + * + * @param NumberConverterInterface $converter + */ + public function setNumberConverter(NumberConverterInterface $converter) + { + $this->numberConverter = $converter; + } + + /** + * Returns the UUID builder this factory uses when creating `Uuid` instances + * + * @return UuidBuilderInterface $builder + */ + public function getUuidBuilder() + { + return $this->uuidBuilder; + } + + /** + * Sets the UUID builder this factory will use when creating `Uuid` instances + * + * @param UuidBuilderInterface $builder + */ + public function setUuidBuilder(UuidBuilderInterface $builder) + { + $this->uuidBuilder = $builder; + } + + /** + * @inheritdoc + */ + public function fromBytes($bytes) + { + return $this->codec->decodeBytes($bytes); + } + + /** + * @inheritdoc + */ + public function fromString($uuid) + { + $uuid = strtolower($uuid); + return $this->codec->decode($uuid); + } + + /** + * @inheritdoc + */ + public function fromInteger($integer) + { + $hex = $this->numberConverter->toHex($integer); + $hex = str_pad($hex, 32, '0', STR_PAD_LEFT); + + return $this->fromString($hex); + } + + /** + * @inheritdoc + */ + public function uuid1($node = null, $clockSeq = null) + { + $bytes = $this->timeGenerator->generate($node, $clockSeq); + $hex = bin2hex($bytes); + + return $this->uuidFromHashedName($hex, 1); + } + + /** + * @inheritdoc + */ + public function uuid3($ns, $name) + { + return $this->uuidFromNsAndName($ns, $name, 3, 'md5'); + } + + /** + * @inheritdoc + */ + public function uuid4() + { + $bytes = $this->randomGenerator->generate(16); + + // When converting the bytes to hex, it turns into a 32-character + // hexadecimal string that looks a lot like an MD5 hash, so at this + // point, we can just pass it to uuidFromHashedName. + $hex = bin2hex($bytes); + + return $this->uuidFromHashedName($hex, 4); + } + + /** + * @inheritdoc + */ + public function uuid5($ns, $name) + { + return $this->uuidFromNsAndName($ns, $name, 5, 'sha1'); + } + + /** + * Returns a `Uuid` + * + * Uses the configured builder and codec and the provided array of hexadecimal + * value UUID fields to construct a `Uuid` object. + * + * @param array $fields An array of fields from which to construct a UUID; + * see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure. + * @return UuidInterface + */ + public function uuid(array $fields) + { + return $this->uuidBuilder->build($this->codec, $fields); + } + + /** + * Returns a version 3 or 5 namespaced `Uuid` + * + * @param string|UuidInterface $ns The UUID namespace to use + * @param string $name The string to hash together with the namespace + * @param int $version The version of UUID to create (3 or 5) + * @param string $hashFunction The hash function to use when hashing together + * the namespace and name + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + protected function uuidFromNsAndName($ns, $name, $version, $hashFunction) + { + if (!($ns instanceof UuidInterface)) { + $ns = $this->codec->decode($ns); + } + + $hash = call_user_func($hashFunction, ($ns->getBytes() . $name)); + + return $this->uuidFromHashedName($hash, $version); + } + + /** + * Returns a `Uuid` created from `$hash` with the version field set to `$version` + * and the variant field set for RFC 4122 + * + * @param string $hash The hash to use when creating the UUID + * @param int $version The UUID version to set for this hash (1, 3, 4, or 5) + * @return UuidInterface + */ + protected function uuidFromHashedName($hash, $version) + { + $timeHi = BinaryUtils::applyVersion(substr($hash, 12, 4), $version); + $clockSeqHi = BinaryUtils::applyVariant(hexdec(substr($hash, 16, 2))); + + $fields = array( + 'time_low' => substr($hash, 0, 8), + 'time_mid' => substr($hash, 8, 4), + 'time_hi_and_version' => str_pad(dechex($timeHi), 4, '0', STR_PAD_LEFT), + 'clock_seq_hi_and_reserved' => str_pad(dechex($clockSeqHi), 2, '0', STR_PAD_LEFT), + 'clock_seq_low' => substr($hash, 18, 2), + 'node' => substr($hash, 20, 12), + ); + + return $this->uuid($fields); + } +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/UuidFactoryInterface.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/UuidFactoryInterface.php new file mode 100644 index 00000000..a228f5bc --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/UuidFactoryInterface.php @@ -0,0 +1,103 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid; + +/** + * UuidFactoryInterface defines common functionality all `UuidFactory` instances + * must implement + */ +interface UuidFactoryInterface +{ + /** + * Generate a version 1 UUID from a host ID, sequence number, and the current time. + * + * @param int|string|null $node A 48-bit number representing the hardware address + * This number may be represented as an integer or a hexadecimal string. + * @param int|null $clockSeq A 14-bit number used to help avoid duplicates that + * could arise when the clock is set backwards in time or if the node ID + * changes. + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if called on a 32-bit system and + * `Moontoast\Math\BigNumber` is not present + * @throws \InvalidArgumentException + * @throws \Exception if it was not possible to gather sufficient entropy + */ + public function uuid1($node = null, $clockSeq = null); + + /** + * Generate a version 3 UUID based on the MD5 hash of a namespace identifier + * (which is a UUID) and a name (which is a string). + * + * @param string $ns The UUID namespace in which to create the named UUID + * @param string $name The name to create a UUID for + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + public function uuid3($ns, $name); + + /** + * Generate a version 4 (random) UUID. + * + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + * @throws \InvalidArgumentException + * @throws \Exception + */ + public function uuid4(); + + /** + * Generate a version 5 UUID based on the SHA-1 hash of a namespace + * identifier (which is a UUID) and a name (which is a string). + * + * @param string $ns The UUID namespace in which to create the named UUID + * @param string $name The name to create a UUID for + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + public function uuid5($ns, $name); + + /** + * Creates a UUID from a byte string. + * + * @param string $bytes A 16-byte string representation of a UUID + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + * @throws \InvalidArgumentException if string has not 16 characters + */ + public function fromBytes($bytes); + + /** + * Creates a UUID from the string standard representation + * + * @param string $uuid A string representation of a UUID + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + public function fromString($uuid); + + /** + * Creates a `Uuid` from an integer representation + * + * The integer representation may be a real integer, a string integer, or + * an integer representation supported by a configured number converter. + * + * @param mixed $integer The integer to use when creating a `Uuid` from an + * integer; may be of any type understood by the configured number converter + * @return UuidInterface + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException + */ + public function fromInteger($integer); +} diff --git a/lam/lib/3rdParty/composer/ramsey/uuid/src/UuidInterface.php b/lam/lib/3rdParty/composer/ramsey/uuid/src/UuidInterface.php new file mode 100644 index 00000000..ea3a46fb --- /dev/null +++ b/lam/lib/3rdParty/composer/ramsey/uuid/src/UuidInterface.php @@ -0,0 +1,270 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid; + +use Ramsey\Uuid\Converter\NumberConverterInterface; +use Ramsey\Uuid\Exception\UnsupportedOperationException; + +/** + * UuidInterface defines common functionality for all universally unique + * identifiers (UUIDs) + */ +interface UuidInterface extends \JsonSerializable, \Serializable +{ + /** + * Compares this UUID to the specified UUID. + * + * The first of two UUIDs is greater than the second if the most + * significant field in which the UUIDs differ is greater for the first + * UUID. + * + * * Q. What's the value of being able to sort UUIDs? + * * A. Use them as keys in a B-Tree or similar mapping. + * + * @param UuidInterface $other UUID to which this UUID is compared + * @return int -1, 0 or 1 as this UUID is less than, equal to, or greater than `$uuid` + */ + public function compareTo(UuidInterface $other); + + /** + * Compares this object to the specified object. + * + * The result is true if and only if the argument is not null, is a UUID + * object, has the same variant, and contains the same value, bit for bit, + * as this UUID. + * + * @param object $other + * @return bool True if `$other` is equal to this UUID + */ + public function equals($other); + + /** + * Returns the UUID as a 16-byte string (containing the six integer fields + * in big-endian byte order). + * + * @return string + */ + public function getBytes(); + + /** + * Returns the number converter to use for converting hex values to/from integers. + * + * @return NumberConverterInterface + */ + public function getNumberConverter(); + + /** + * Returns the hexadecimal value of the UUID. + * + * @return string + */ + public function getHex(); + + /** + * Returns an array of the fields of this UUID, with keys named according + * to the RFC 4122 names for the fields. + * + * * **time_low**: The low field of the timestamp, an unsigned 32-bit integer + * * **time_mid**: The middle field of the timestamp, an unsigned 16-bit integer + * * **time_hi_and_version**: The high field of the timestamp multiplexed with + * the version number, an unsigned 16-bit integer + * * **clock_seq_hi_and_reserved**: The high field of the clock sequence + * multiplexed with the variant, an unsigned 8-bit integer + * * **clock_seq_low**: The low field of the clock sequence, an unsigned + * 8-bit integer + * * **node**: The spatially unique node identifier, an unsigned 48-bit + * integer + * + * @return array The UUID fields represented as hexadecimal values + */ + public function getFieldsHex(); + + /** + * Returns the high field of the clock sequence multiplexed with the variant + * (bits 65-72 of the UUID). + * + * @return string Hexadecimal value of clock_seq_hi_and_reserved + */ + public function getClockSeqHiAndReservedHex(); + + /** + * Returns the low field of the clock sequence (bits 73-80 of the UUID). + * + * @return string Hexadecimal value of clock_seq_low + */ + public function getClockSeqLowHex(); + + /** + * Returns the clock sequence value associated with this UUID. + * + * @return string Hexadecimal value of clock sequence + */ + public function getClockSequenceHex(); + + /** + * Returns a PHP `DateTime` object representing the timestamp associated + * with this UUID. + * + * The timestamp value is only meaningful in a time-based UUID, which + * has version type 1. If this UUID is not a time-based UUID then + * this method throws `UnsupportedOperationException`. + * + * @return \DateTime A PHP DateTime representation of the date + * @throws UnsupportedOperationException If this UUID is not a version 1 UUID + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if called in a 32-bit system and + * `Moontoast\Math\BigNumber` is not present + */ + public function getDateTime(); + + /** + * Returns the integer value of the UUID, converted to an appropriate number + * representation. + * + * @return mixed Converted representation of the unsigned 128-bit integer value + * @throws \Ramsey\Uuid\Exception\UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + */ + public function getInteger(); + + /** + * Returns the least significant 64 bits of this UUID's 128 bit value. + * + * @return string Hexadecimal value of least significant bits + */ + public function getLeastSignificantBitsHex(); + + /** + * Returns the most significant 64 bits of this UUID's 128 bit value. + * + * @return string Hexadecimal value of most significant bits + */ + public function getMostSignificantBitsHex(); + + /** + * Returns the node value associated with this UUID + * + * For UUID version 1, the node field consists of an IEEE 802 MAC + * address, usually the host address. For systems with multiple IEEE + * 802 addresses, any available one can be used. The lowest addressed + * octet (octet number 10) contains the global/local bit and the + * unicast/multicast bit, and is the first octet of the address + * transmitted on an 802.3 LAN. + * + * For systems with no IEEE address, a randomly or pseudo-randomly + * generated value may be used; see RFC 4122, Section 4.5. The + * multicast bit must be set in such addresses, in order that they + * will never conflict with addresses obtained from network cards. + * + * For UUID version 3 or 5, the node field is a 48-bit value constructed + * from a name as described in RFC 4122, Section 4.3. + * + * For UUID version 4, the node field is a randomly or pseudo-randomly + * generated 48-bit value as described in RFC 4122, Section 4.4. + * + * @return string Hexadecimal value of node + * @link http://tools.ietf.org/html/rfc4122#section-4.1.6 + */ + public function getNodeHex(); + + /** + * Returns the high field of the timestamp multiplexed with the version + * number (bits 49-64 of the UUID). + * + * @return string Hexadecimal value of time_hi_and_version + */ + public function getTimeHiAndVersionHex(); + + /** + * Returns the low field of the timestamp (the first 32 bits of the UUID). + * + * @return string Hexadecimal value of time_low + */ + public function getTimeLowHex(); + + /** + * Returns the middle field of the timestamp (bits 33-48 of the UUID). + * + * @return string Hexadecimal value of time_mid + */ + public function getTimeMidHex(); + + /** + * Returns the timestamp value associated with this UUID. + * + * The 60 bit timestamp value is constructed from the time_low, + * time_mid, and time_hi fields of this UUID. The resulting + * timestamp is measured in 100-nanosecond units since midnight, + * October 15, 1582 UTC. + * + * The timestamp value is only meaningful in a time-based UUID, which + * has version type 1. If this UUID is not a time-based UUID then + * this method throws UnsupportedOperationException. + * + * @return string Hexadecimal value of the timestamp + * @throws UnsupportedOperationException If this UUID is not a version 1 UUID + * @link http://tools.ietf.org/html/rfc4122#section-4.1.4 + */ + public function getTimestampHex(); + + /** + * Returns the string representation of the UUID as a URN. + * + * @return string + * @link http://en.wikipedia.org/wiki/Uniform_Resource_Name + */ + public function getUrn(); + + /** + * Returns the variant number associated with this UUID. + * + * The variant number describes the layout of the UUID. The variant + * number has the following meaning: + * + * * 0 - Reserved for NCS backward compatibility + * * 2 - The RFC 4122 variant (used by this class) + * * 6 - Reserved, Microsoft Corporation backward compatibility + * * 7 - Reserved for future definition + * + * @return int + * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 + */ + public function getVariant(); + + /** + * Returns the version number associated with this UUID. + * + * The version number describes how this UUID was generated and has the + * following meaning: + * + * * 1 - Time-based UUID + * * 2 - DCE security UUID + * * 3 - Name-based UUID hashed with MD5 + * * 4 - Randomly generated UUID + * * 5 - Name-based UUID hashed with SHA-1 + * + * Returns null if this UUID is not an RFC 4122 variant, since version + * is only meaningful for this variant. + * + * @return int|null + * @link http://tools.ietf.org/html/rfc4122#section-4.1.3 + */ + public function getVersion(); + + /** + * Converts this UUID into a string representation. + * + * @return string + */ + public function toString(); +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/base64url/.php_cs.dist b/lam/lib/3rdParty/composer/spomky-labs/base64url/.php_cs.dist new file mode 100644 index 00000000..a264e013 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/base64url/.php_cs.dist @@ -0,0 +1,61 @@ +in(__DIR__.'/src') + ->in(__DIR__.'/tests') +; + +return PhpCsFixer\Config::create() + ->setRules([ + '@PSR1' => true, + '@PSR2' => true, + '@Symfony' => true, + '@DoctrineAnnotation' => true, + '@PHP70Migration' => true, + '@PHP71Migration' => true, + 'strict_param' => true, + 'strict_comparison' => true, + 'array_syntax' => ['syntax' => 'short'], + 'array_indentation' => true, + 'ordered_imports' => true, + 'protected_to_private' => true, + 'declare_strict_types' => true, + 'native_function_invocation' => [ + 'include' => ['@compiler_optimized'], + 'scope' => 'namespaced', + ], + 'mb_str_functions' => true, + 'method_chaining_indentation' => true, + 'linebreak_after_opening_tag' => true, + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'compact_nullable_typehint' => true, + 'no_superfluous_phpdoc_tags' => true, + 'no_superfluous_elseif' => true, + 'phpdoc_trim_consecutive_blank_line_separation' => true, + 'phpdoc_order' => true, + 'pow_to_exponentiation' => true, + 'simplified_null_return' => true, + 'header_comment' => [ + 'header' => $header, + ], + 'align_multiline_comment' => [ + 'comment_type' => 'all_multiline', + ], + 'php_unit_test_annotation' => [ + 'case' => 'snake', + 'style' => 'annotation', + ], + 'php_unit_test_case_static_method_calls' => true, + ]) + ->setRiskyAllowed(true) + ->setUsingCache(true) + ->setFinder($finder) + ; diff --git a/lam/lib/3rdParty/composer/spomky-labs/base64url/LICENSE b/lam/lib/3rdParty/composer/spomky-labs/base64url/LICENSE new file mode 100644 index 00000000..506ff485 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/base64url/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014-2018 Spomky-Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/lam/lib/3rdParty/composer/spomky-labs/base64url/composer.json b/lam/lib/3rdParty/composer/spomky-labs/base64url/composer.json new file mode 100644 index 00000000..72074e95 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/base64url/composer.json @@ -0,0 +1,31 @@ +{ + "name": "spomky-labs/base64url", + "description": "Base 64 URL Safe Encoding/Decoding PHP Library", + "type": "library", + "license": "MIT", + "keywords": ["Base64", "URL", "Safe", "RFC4648"], + "homepage": "https://github.com/Spomky-Labs/base64url", + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky-Labs/base64url/contributors" + } + ], + "autoload": { + "psr-4": { + "Base64Url\\": "src/" + } + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0", + "php-coveralls/php-coveralls": "^2.0" + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/base64url/src/Base64Url.php b/lam/lib/3rdParty/composer/spomky-labs/base64url/src/Base64Url.php new file mode 100644 index 00000000..efa9ecbc --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/base64url/src/Base64Url.php @@ -0,0 +1,50 @@ + diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/.github/PULL_REQUEST_TEMPLATE.md b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..c4218d73 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ +| Q | A +| ------------- | --- +| Bug fix? | yes/no +| New feature? | yes/no +| BC breaks? | yes/no +| Deprecations? | yes/no +| Tests added | +| Doc PR | + + diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/.php_cs.dist b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/.php_cs.dist new file mode 100644 index 00000000..8591c7a3 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/.php_cs.dist @@ -0,0 +1,61 @@ +in('src') + ->in('tests') +; + +return PhpCsFixer\Config::create() + ->setRules([ + '@PSR1' => true, + '@PSR2' => true, + '@Symfony' => true, + '@DoctrineAnnotation' => true, + '@PHP70Migration' => true, + '@PHP71Migration' => true, + 'strict_param' => true, + 'strict_comparison' => true, + 'array_syntax' => ['syntax' => 'short'], + 'array_indentation' => true, + 'ordered_imports' => true, + 'protected_to_private' => true, + 'declare_strict_types' => true, + 'native_function_invocation' => [ + 'include' => ['@compiler_optimized'], + 'scope' => 'namespaced', + ], + 'mb_str_functions' => true, + 'method_chaining_indentation' => true, + 'linebreak_after_opening_tag' => true, + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'compact_nullable_typehint' => true, + 'no_superfluous_phpdoc_tags' => true, + 'no_superfluous_elseif' => true, + 'phpdoc_trim_consecutive_blank_line_separation' => true, + 'phpdoc_order' => true, + 'pow_to_exponentiation' => true, + 'simplified_null_return' => true, + 'header_comment' => [ + 'header' => $header, + ], + 'align_multiline_comment' => [ + 'comment_type' => 'all_multiline', + ], + 'php_unit_test_annotation' => [ + 'case' => 'snake', + 'style' => 'annotation', + ], + 'php_unit_test_case_static_method_calls' => true, + ]) + ->setRiskyAllowed(true) + ->setUsingCache(true) + ->setFinder($finder) + ; diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/CODE_OF_CONDUCT.md b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..4ec12c72 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@spomky-labs.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/LICENSE b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/LICENSE new file mode 100644 index 00000000..25cfdd66 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Spomky-Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/composer.json b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/composer.json new file mode 100644 index 00000000..1ea25622 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/composer.json @@ -0,0 +1,50 @@ +{ + "name": "spomky-labs/cbor-php", + "type": "library", + "license": "MIT", + "keywords": ["CBOR", "Concise Binary Object Representation", "RFC7049"], + "description": "CBOR Encoder/Decoder for PHP", + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + },{ + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/cbor-php/contributors" + } + ], + "autoload": { + "psr-4": { + "CBOR\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "CBOR\\Test\\": "tests/" + } + }, + "require": { + "php": "^7.1|^8.0", + "spomky-labs/base64url": "^1.0|^2.0", + "ext-gmp": "*", + "beberlei/assert": "^3.2" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-beberlei-assert": "^0.11.0", + "phpstan/phpstan-deprecation-rules": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11", + "phpunit/phpunit": "^7.5|^8.0", + "rector/rector": "^0.5" + }, + "suggest": { + "ext-bcmath": "BCMath extension needed to handle the Big Float and Decimal Fraction Tags" + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/infection.json.dist b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/infection.json.dist new file mode 100644 index 00000000..7084dd28 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/infection.json.dist @@ -0,0 +1,11 @@ +{ + "timeout":20, + "source": { + "directories": [ + "src" + ] + }, + "logs": { + "text": "infection-log.txt" + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/phpstan.neon b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/phpstan.neon new file mode 100644 index 00000000..f28b5123 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/phpstan.neon @@ -0,0 +1,11 @@ +parameters: + level: 7 + paths: + - src + ignoreErrors: + - '#Method CBOR\\OtherObject\\DoublePrecisionFloatObject::rightShift\(\) should return GMP but returns resource\.#' +includes: + - vendor/phpstan/phpstan-strict-rules/rules.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-deprecation-rules/rules.neon + - vendor/phpstan/phpstan-beberlei-assert/extension.neon diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/AbstractCBORObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/AbstractCBORObject.php new file mode 100644 index 00000000..1cf7deba --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/AbstractCBORObject.php @@ -0,0 +1,48 @@ +majorType = $majorType; + $this->additionalInformation = $additionalInformation; + } + + public function getMajorType(): int + { + return $this->majorType; + } + + public function getAdditionalInformation(): int + { + return $this->additionalInformation; + } + + public function __toString(): string + { + return \chr($this->majorType << 5 | $this->additionalInformation); + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/ByteStringObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/ByteStringObject.php new file mode 100644 index 00000000..930074c1 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/ByteStringObject.php @@ -0,0 +1,64 @@ +length = $length; + $this->value = $data; + } + + public function getValue(): string + { + return $this->value; + } + + public function getLength(): int + { + return mb_strlen($this->value, '8bit'); + } + + public function getNormalizedData(bool $ignoreTags = false): string + { + return $this->value; + } + + public function __toString(): string + { + $result = parent::__toString(); + if (null !== $this->length) { + $result .= $this->length; + } + $result .= $this->value; + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/ByteStringWithChunkObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/ByteStringWithChunkObject.php new file mode 100644 index 00000000..9efec5fd --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/ByteStringWithChunkObject.php @@ -0,0 +1,87 @@ +chunks[] = $chunk; + } + + public function append(string $chunk): void + { + $this->add(new ByteStringObject($chunk)); + } + + public function getValue(): string + { + $result = ''; + foreach ($this->chunks as $chunk) { + $result .= $chunk->getValue(); + } + + return $result; + } + + public function getLength(): int + { + $length = 0; + foreach ($this->chunks as $chunk) { + $length += $chunk->getLength(); + } + + return $length; + } + + public function getNormalizedData(bool $ignoreTags = false): string + { + $result = ''; + foreach ($this->chunks as $chunk) { + $result .= $chunk->getNormalizedData($ignoreTags); + } + + return $result; + } + + public function __toString(): string + { + $result = parent::__toString(); + foreach ($this->chunks as $chunk) { + $result .= (string) $chunk; + } + $bin = hex2bin('FF'); + if (false === $bin) { + throw new InvalidArgumentException('Unable to convert the data'); + } + $result .= $bin; + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/CBORObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/CBORObject.php new file mode 100644 index 00000000..09e94ea8 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/CBORObject.php @@ -0,0 +1,28 @@ +tagObjectManager = $tagObjectManager; + $this->otherTypeManager = $otherTypeManager; + } + + public function decode(Stream $stream): CBORObject + { + return $this->process($stream); + } + + private function process(Stream $stream, bool $breakable = false): CBORObject + { + $ib = \ord($stream->read(1)); + $mt = $ib >> 5; + $ai = $ib & 0b00011111; + $val = null; + switch ($ai) { + case 0b00011000: //24 + case 0b00011001: //25 + case 0b00011010: //26 + case 0b00011011: //27 + $val = $stream->read(2 ** ($ai & 0b00000111)); + break; + case 0b00011100: //28 + case 0b00011101: //29 + case 0b00011110: //30 + throw new InvalidArgumentException(sprintf('Cannot parse the data. Found invalid Additional Information "%s" (%d).', str_pad(decbin($ai), 5, '0', STR_PAD_LEFT), $ai)); + case 0b00011111: //31 + return $this->processInfinite($stream, $mt, $breakable); + } + + return $this->processFinite($stream, $mt, $ai, $val); + } + + private function processFinite(Stream $stream, int $mt, int $ai, ?string $val): CBORObject + { + switch ($mt) { + case 0b000: //0 + return UnsignedIntegerObject::createObjectForValue($ai, $val); + case 0b001: //1 + return SignedIntegerObject::createObjectForValue($ai, $val); + case 0b010: //2 + $length = null === $val ? $ai : gmp_intval(gmp_init(bin2hex($val), 16)); + + return new ByteStringObject($stream->read($length)); + case 0b011: //3 + $length = null === $val ? $ai : gmp_intval(gmp_init(bin2hex($val), 16)); + + return new TextStringObject($stream->read($length)); + case 0b100: //4 + $object = new ListObject(); + $nbItems = null === $val ? $ai : gmp_intval(gmp_init(bin2hex($val), 16)); + for ($i = 0; $i < $nbItems; ++$i) { + $object->add($this->process($stream)); + } + + return $object; + case 0b101: //5 + $object = new MapObject(); + $nbItems = null === $val ? $ai : gmp_intval(gmp_init(bin2hex($val), 16)); + for ($i = 0; $i < $nbItems; ++$i) { + $object->add($this->process($stream), $this->process($stream)); + } + + return $object; + case 0b110: //6 + return $this->tagObjectManager->createObjectForValue($ai, $val, $this->process($stream)); + case 0b111: //7 + return $this->otherTypeManager->createObjectForValue($ai, $val); + default: + throw new RuntimeException(sprintf('Unsupported major type "%s" (%d).', str_pad(decbin($mt), 5, '0', STR_PAD_LEFT), $mt)); // Should never append + } + } + + private function processInfinite(Stream $stream, int $mt, bool $breakable): CBORObject + { + switch ($mt) { + case 0b010: //2 + $object = new ByteStringWithChunkObject(); + while (!($it = $this->process($stream, true)) instanceof BreakObject) { + if (!$it instanceof ByteStringObject) { + throw new RuntimeException('Unable to parse the data. Infinite Byte String object can only get Byte String objects.'); + } + $object->add($it); + } + + return $object; + case 0b011: //3 + $object = new TextStringWithChunkObject(); + while (!($it = $this->process($stream, true)) instanceof BreakObject) { + if (!$it instanceof TextStringObject) { + throw new RuntimeException('Unable to parse the data. Infinite Text String object can only get Text String objects.'); + } + $object->add($it); + } + + return $object; + case 0b100: //4 + $object = new InfiniteListObject(); + while (!($it = $this->process($stream, true)) instanceof BreakObject) { + $object->add($it); + } + + return $object; + case 0b101: //5 + $object = new InfiniteMapObject(); + while (!($it = $this->process($stream, true)) instanceof BreakObject) { + $object->append($it, $this->process($stream)); + } + + return $object; + case 0b111: //7 + if (!$breakable) { + throw new InvalidArgumentException('Cannot parse the data. No enclosing indefinite.'); + } + + return new BreakObject(); + case 0b000: //0 + case 0b001: //1 + case 0b110: //6 + default: + throw new InvalidArgumentException(sprintf('Cannot parse the data. Found infinite length for Major Type "%s" (%d).', str_pad(decbin($mt), 5, '0', STR_PAD_LEFT), $mt)); + } + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/InfiniteListObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/InfiniteListObject.php new file mode 100644 index 00000000..f8b042bb --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/InfiniteListObject.php @@ -0,0 +1,69 @@ +getNormalizedData($ignoreTags); + }, $this->data); + } + + public function add(CBORObject $item): void + { + $this->data[] = $item; + } + + public function count(): int + { + return \count($this->data); + } + + public function getIterator(): \Iterator + { + return new \ArrayIterator($this->data); + } + + public function __toString(): string + { + $result = parent::__toString(); + foreach ($this->data as $object) { + $result .= (string) $object; + } + $bin = hex2bin('FF'); + if (false === $bin) { + throw new InvalidArgumentException('Unable to convert the data'); + } + $result .= $bin; + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/InfiniteMapObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/InfiniteMapObject.php new file mode 100644 index 00000000..68a3381e --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/InfiniteMapObject.php @@ -0,0 +1,73 @@ +data[] = new MapItem($key, $value); + } + + public function count(): int + { + return \count($this->data); + } + + public function getIterator(): \Iterator + { + return new \ArrayIterator($this->data); + } + + public function getNormalizedData(bool $ignoreTags = false): array + { + $result = []; + foreach ($this->data as $object) { + $result[$object->getKey()->getNormalizedData($ignoreTags)] = $object->getValue()->getNormalizedData($ignoreTags); + } + + return $result; + } + + public function __toString(): string + { + $result = parent::__toString(); + foreach ($this->data as $object) { + $result .= (string) $object->getKey(); + $result .= (string) $object->getValue(); + } + $bin = hex2bin('FF'); + if (false === $bin) { + throw new InvalidArgumentException('Unable to convert the data'); + } + $result .= $bin; + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/LengthCalculator.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/LengthCalculator.php new file mode 100644 index 00000000..310be254 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/LengthCalculator.php @@ -0,0 +1,66 @@ +data = $data; + $this->length = $length; + } + + public function add(CBORObject $object): void + { + $this->data[] = $object; + list($this->additionalInformation, $this->length) = LengthCalculator::getLengthOfArray($this->data); + } + + public function get(int $index): CBORObject + { + if (!\array_key_exists($index, $this->data)) { + throw new InvalidArgumentException('Index not found.'); + } + + return $this->data[$index]; + } + + public function getNormalizedData(bool $ignoreTags = false): array + { + return array_map(function (CBORObject $item) use ($ignoreTags) { + return $item->getNormalizedData($ignoreTags); + }, $this->data); + } + + public function count(): int + { + return \count($this->data); + } + + public function getIterator(): \Iterator + { + return new \ArrayIterator($this->data); + } + + public function __toString(): string + { + $result = parent::__toString(); + if (null !== $this->length) { + $result .= $this->length; + } + foreach ($this->data as $object) { + $result .= (string) $object; + } + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/MapItem.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/MapItem.php new file mode 100644 index 00000000..686baa37 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/MapItem.php @@ -0,0 +1,43 @@ +key = $key; + $this->value = $value; + } + + public function getKey(): CBORObject + { + return $this->key; + } + + public function getValue(): CBORObject + { + return $this->value; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/MapObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/MapObject.php new file mode 100644 index 00000000..45f145a9 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/MapObject.php @@ -0,0 +1,88 @@ +data = $data; + $this->length = $length; + } + + public function add(CBORObject $key, CBORObject $value): void + { + $this->data[] = new MapItem($key, $value); + list($this->additionalInformation, $this->length) = LengthCalculator::getLengthOfArray($this->data); + } + + public function count(): int + { + return \count($this->data); + } + + public function getIterator(): \Iterator + { + return new \ArrayIterator($this->data); + } + + public function getNormalizedData(bool $ignoreTags = false): array + { + $result = []; + foreach ($this->data as $object) { + $result[$object->getKey()->getNormalizedData($ignoreTags)] = $object->getValue()->getNormalizedData($ignoreTags); + } + + return $result; + } + + public function __toString(): string + { + $result = parent::__toString(); + if (null !== $this->length) { + $result .= $this->length; + } + foreach ($this->data as $object) { + $result .= (string) $object->getKey(); + $result .= (string) $object->getValue(); + } + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject.php new file mode 100644 index 00000000..baf034a0 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject.php @@ -0,0 +1,47 @@ +data = $data; + } + + public function __toString(): string + { + $result = parent::__toString(); + if (null !== $this->data) { + $result .= $this->data; + } + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/BreakObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/BreakObject.php new file mode 100644 index 00000000..e6d59c6c --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/BreakObject.php @@ -0,0 +1,39 @@ +data; + Assertion::string($data, 'Invalid data'); + $single = gmp_init(bin2hex($data), 16); + $exp = gmp_intval($this->bitwiseAnd($this->rightShift($single, 52), gmp_init('7ff', 16))); + $mant = gmp_intval($this->bitwiseAnd($single, gmp_init('fffffffffffff', 16))); + $sign = gmp_intval($this->rightShift($single, 63)); + + if (0 === $exp) { + $val = $mant * 2 ** (-(1022 + 52)); + } elseif (0b11111111111 !== $exp) { + $val = ($mant + (1 << 52)) * 2 ** ($exp - (1023 + 52)); + } else { + $val = 0 === $mant ? INF : NAN; + } + + return 1 === $sign ? -$val : $val; + } + + public function getExponent(): int + { + $data = $this->data; + Assertion::string($data, 'Invalid data'); + $single = gmp_intval(gmp_init(bin2hex($data), 16)); + + return ($single >> 52) & 0x7ff; + } + + public function getMantissa(): int + { + $data = $this->data; + Assertion::string($data, 'Invalid data'); + $single = gmp_intval(gmp_init(bin2hex($data), 16)); + + return $single & 0x7fffff; + } + + public function getSign(): int + { + $data = $this->data; + Assertion::string($data, 'Invalid data'); + $single = gmp_intval(gmp_init(bin2hex($data), 16)); + + return 1 === ($single >> 63) ? -1 : 1; + } + + private function rightShift(\GMP $number, int $positions): \GMP + { + return gmp_div($number, gmp_pow(gmp_init(2, 10), $positions)); + } + + private function bitwiseAnd(\GMP $first, \GMP $other): \GMP + { + return gmp_and($first, $other); + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/FalseObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/FalseObject.php new file mode 100644 index 00000000..bbc3e15c --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/FalseObject.php @@ -0,0 +1,39 @@ +data; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/HalfPrecisionFloatObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/HalfPrecisionFloatObject.php new file mode 100644 index 00000000..6afd3895 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/HalfPrecisionFloatObject.php @@ -0,0 +1,89 @@ +data; + Assertion::string($data, 'Invalid data'); + $half = gmp_intval(gmp_init(bin2hex($data), 16)); + $exp = ($half >> 10) & 0x1f; + $mant = $half & 0x3ff; + + if (0 === $exp) { + $val = $mant * 2 ** (-24); + } elseif (0b11111 !== $exp) { + $val = ($mant + (1 << 10)) * 2 ** ($exp - 25); + } else { + $val = 0 === $mant ? INF : NAN; + } + + return 1 === ($half >> 15) ? -$val : $val; + } + + public function getExponent(): int + { + $data = $this->data; + Assertion::string($data, 'Invalid data'); + $half = gmp_intval(gmp_init(bin2hex($data), 16)); + + return ($half >> 10) & 0x1f; + } + + public function getMantissa(): int + { + $data = $this->data; + Assertion::string($data, 'Invalid data'); + $half = gmp_intval(gmp_init(bin2hex($data), 16)); + + return $half & 0x3ff; + } + + public function getSign(): int + { + $data = $this->data; + Assertion::string($data, 'Invalid data'); + $half = gmp_intval(gmp_init(bin2hex($data), 16)); + + return 1 === ($half >> 15) ? -1 : 1; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/NullObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/NullObject.php new file mode 100644 index 00000000..ffbdd912 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/NullObject.php @@ -0,0 +1,38 @@ +classes[$ai] = $class; + } + } + + public function getClassForValue(int $value): string + { + return \array_key_exists($value, $this->classes) ? $this->classes[$value] : GenericObject::class; + } + + public function createObjectForValue(int $value, ?string $data): OtherObject + { + /** @var OtherObject $class */ + $class = $this->getClassForValue($value); + + return $class::createFromLoadedData($value, $data); + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/SimpleObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/SimpleObject.php new file mode 100644 index 00000000..ff8a690f --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/SimpleObject.php @@ -0,0 +1,54 @@ +data) { + return $this->getAdditionalInformation(); + } + + return gmp_intval(gmp_init(bin2hex($this->data), 16)); + } + + /** + * @return SimpleObject + */ + public static function create(int $value): self + { + switch (true) { + case $value < 24: + return new self($value, null); + case $value < 256: + return new self(24, \chr($value)); + default: + throw new InvalidArgumentException('The value is not a valid simple value'); + } + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/SinglePrecisionFloatObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/SinglePrecisionFloatObject.php new file mode 100644 index 00000000..e9c56996 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/SinglePrecisionFloatObject.php @@ -0,0 +1,89 @@ +data; + Assertion::string($data, 'Invalid data'); + $single = gmp_intval(gmp_init(bin2hex($data), 16)); + $exp = ($single >> 23) & 0xff; + $mant = $single & 0x7fffff; + + if (0 === $exp) { + $val = $mant * 2 ** (-(126 + 23)); + } elseif (0b11111111 !== $exp) { + $val = ($mant + (1 << 23)) * 2 ** ($exp - (127 + 23)); + } else { + $val = 0 === $mant ? INF : NAN; + } + + return 1 === ($single >> 31) ? -$val : $val; + } + + public function getExponent(): int + { + $data = $this->data; + Assertion::string($data, 'Invalid data'); + $single = gmp_intval(gmp_init(bin2hex($data), 16)); + + return ($single >> 23) & 0xff; + } + + public function getMantissa(): int + { + $data = $this->data; + Assertion::string($data, 'Invalid data'); + $single = gmp_intval(gmp_init(bin2hex($data), 16)); + + return $single & 0x7fffff; + } + + public function getSign(): int + { + $data = $this->data; + Assertion::string($data, 'Invalid data'); + $single = gmp_intval(gmp_init(bin2hex($data), 16)); + + return 1 === ($single >> 31) ? -1 : 1; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/TrueObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/TrueObject.php new file mode 100644 index 00000000..1e354db3 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/OtherObject/TrueObject.php @@ -0,0 +1,39 @@ +data = $data; + } + + public static function createObjectForValue(int $additionalInformation, ?string $data): self + { + return new self($additionalInformation, $data); + } + + public static function create(int $value): self + { + return self::createFromGmpValue(gmp_init($value)); + } + + public static function createFromGmpValue(\GMP $value): self + { + if (gmp_cmp($value, gmp_init(0)) >= 0) { + throw new InvalidArgumentException('The value must be a negative integer.'); + } + + $minusOne = gmp_init(-1); + $computed_value = gmp_sub($minusOne, $value); + + switch (true) { + case gmp_intval($computed_value) < 24: + $ai = gmp_intval($computed_value); + $data = null; + break; + case gmp_cmp($computed_value, gmp_init('FF', 16)) < 0: + $ai = 24; + $data = self::hex2bin(str_pad(gmp_strval($computed_value, 16), 2, '0', STR_PAD_LEFT)); + break; + case gmp_cmp($computed_value, gmp_init('FFFF', 16)) < 0: + $ai = 25; + $data = self::hex2bin(str_pad(gmp_strval($computed_value, 16), 4, '0', STR_PAD_LEFT)); + break; + case gmp_cmp($computed_value, gmp_init('FFFFFFFF', 16)) < 0: + $ai = 26; + $data = self::hex2bin(str_pad(gmp_strval($computed_value, 16), 8, '0', STR_PAD_LEFT)); + break; + default: + throw new InvalidArgumentException('Out of range. Please use NegativeBigIntegerTag tag with ByteStringObject object instead.'); + } + + return new self($ai, $data); + } + + public function getValue(): string + { + return $this->getNormalizedData(); + } + + public function getNormalizedData(bool $ignoreTags = false): string + { + if (null === $this->data) { + return (string) (-1 - $this->additionalInformation); + } + + $result = gmp_init(bin2hex($this->data), 16); + $minusOne = gmp_init(-1); + $result = gmp_sub($minusOne, $result); + + return gmp_strval($result, 10); + } + + public function __toString(): string + { + $result = parent::__toString(); + if (null !== $this->data) { + $result .= $this->data; + } + + return $result; + } + + private static function hex2bin(string $data): string + { + $result = hex2bin($data); + if (false === $result) { + throw new InvalidArgumentException('Unable to convert the data'); + } + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Stream.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Stream.php new file mode 100644 index 00000000..47c7387e --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Stream.php @@ -0,0 +1,19 @@ +resource = $resource; + } + + public function read(int $length): string + { + if (0 === $length) { + return ''; + } + $data = fread($this->resource, $length); + if (false === $data) { + throw new RuntimeException('Unable to read the memory'); + } + if (mb_strlen($data, '8bit') !== $length) { + throw new InvalidArgumentException(sprintf('Out of range. Expected: %d, read: %d.', $length, mb_strlen($data, '8bit'))); + } + + return $data; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/Base16EncodingTag.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/Base16EncodingTag.php new file mode 100644 index 00000000..205a82c5 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/Base16EncodingTag.php @@ -0,0 +1,57 @@ +object->getNormalizedData($ignoreTags); + } + + if (!$this->object instanceof ByteStringObject && !$this->object instanceof ByteStringWithChunkObject && !$this->object instanceof TextStringObject && !$this->object instanceof TextStringWithChunkObject) { + return $this->object->getNormalizedData($ignoreTags); + } + + return bin2hex($this->object->getNormalizedData($ignoreTags)); + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/Base64EncodingTag.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/Base64EncodingTag.php new file mode 100644 index 00000000..3187a64f --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/Base64EncodingTag.php @@ -0,0 +1,62 @@ +object->getNormalizedData($ignoreTags); + } + + if (!$this->object instanceof ByteStringObject && !$this->object instanceof ByteStringWithChunkObject && !$this->object instanceof TextStringObject && !$this->object instanceof TextStringWithChunkObject) { + return $this->object->getNormalizedData($ignoreTags); + } + + $result = \base64_decode($this->object->getNormalizedData($ignoreTags), true); + if (false === $result) { + throw new InvalidArgumentException('Unable to decode the data'); + } + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/Base64UrlEncodingTag.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/Base64UrlEncodingTag.php new file mode 100644 index 00000000..7481948c --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/Base64UrlEncodingTag.php @@ -0,0 +1,58 @@ +object->getNormalizedData($ignoreTags); + } + + if (!$this->object instanceof ByteStringObject && !$this->object instanceof ByteStringWithChunkObject && !$this->object instanceof TextStringObject && !$this->object instanceof TextStringWithChunkObject) { + return $this->object->getNormalizedData($ignoreTags); + } + + return Base64Url::decode($this->object->getNormalizedData($ignoreTags)); + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/BigFloatTag.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/BigFloatTag.php new file mode 100644 index 00000000..b14c75bb --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/BigFloatTag.php @@ -0,0 +1,100 @@ +get(0); + if (!$e instanceof UnsignedIntegerObject && !$e instanceof SignedIntegerObject) { + throw new InvalidArgumentException('The exponent must be a Signed Integer or an Unsigned Integer object.'); + } + $m = $object->get(1); + if (!$m instanceof UnsignedIntegerObject && !$m instanceof SignedIntegerObject && !$m instanceof NegativeBigIntegerTag && !$m instanceof PositiveBigIntegerTag) { + throw new InvalidArgumentException('The mantissa must be a Positive or Negative Signed Integer or an Unsigned Integer object.'); + } + + return new self(5, null, $object); + } + + public static function createFromExponentAndMantissa(CBORObject $e, CBORObject $m): Base + { + $object = new ListObject(); + $object->add($e); + $object->add($m); + + return self::create($object); + } + + public function getNormalizedData(bool $ignoreTags = false) + { + if ($ignoreTags) { + return $this->object->getNormalizedData($ignoreTags); + } + + if (!$this->object instanceof ListObject || 2 !== \count($this->object)) { + return $this->object->getNormalizedData($ignoreTags); + } + $e = $this->object->get(0); + $m = $this->object->get(1); + + if (!$e instanceof UnsignedIntegerObject && !$e instanceof SignedIntegerObject) { + return $this->object->getNormalizedData($ignoreTags); + } + if (!$m instanceof UnsignedIntegerObject && !$m instanceof SignedIntegerObject && !$m instanceof NegativeBigIntegerTag && !$m instanceof PositiveBigIntegerTag) { + return $this->object->getNormalizedData($ignoreTags); + } + + return rtrim( + bcmul( + $m->getNormalizedData($ignoreTags), + bcpow( + '2', + $e->getNormalizedData($ignoreTags), + 100), + 100), + '0' + ); + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/DecimalFractionTag.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/DecimalFractionTag.php new file mode 100644 index 00000000..c9f9094a --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/DecimalFractionTag.php @@ -0,0 +1,95 @@ +get(0); + if (!$e instanceof UnsignedIntegerObject && !$e instanceof SignedIntegerObject) { + throw new InvalidArgumentException('The exponent must be a Signed Integer or an Unsigned Integer object.'); + } + $m = $object->get(1); + if (!$m instanceof UnsignedIntegerObject && !$m instanceof SignedIntegerObject && !$m instanceof NegativeBigIntegerTag && !$m instanceof PositiveBigIntegerTag) { + throw new InvalidArgumentException('The mantissa must be a Positive or Negative Signed Integer or an Unsigned Integer object.'); + } + + parent::__construct(4, null, $object); + } + + public static function createFromExponentAndMantissa(CBORObject $e, CBORObject $m): Base + { + $object = new ListObject(); + $object->add($e); + $object->add($m); + + return new self($object); + } + + public function getNormalizedData(bool $ignoreTags = false) + { + if ($ignoreTags) { + return $this->object->getNormalizedData($ignoreTags); + } + + if (!$this->object instanceof ListObject || 2 !== \count($this->object)) { + return $this->object->getNormalizedData($ignoreTags); + } + $e = $this->object->get(0); + $m = $this->object->get(1); + + if (!$e instanceof UnsignedIntegerObject && !$e instanceof SignedIntegerObject) { + return $this->object->getNormalizedData($ignoreTags); + } + if (!$m instanceof UnsignedIntegerObject && !$m instanceof SignedIntegerObject && !$m instanceof NegativeBigIntegerTag && !$m instanceof PositiveBigIntegerTag) { + return $this->object->getNormalizedData($ignoreTags); + } + + return rtrim( + bcmul( + $m->getNormalizedData($ignoreTags), + bcpow( + '10', + $e->getNormalizedData($ignoreTags), + 100), + 100), + '0' + ); + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/EpochTag.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/EpochTag.php new file mode 100644 index 00000000..8b9c1f7f --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/EpochTag.php @@ -0,0 +1,44 @@ +object->getNormalizedData($ignoreTags); + } + + return \DateTimeImmutable::createFromFormat(DATE_RFC3339, $this->object->getNormalizedData($ignoreTags)); + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/GenericTag.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/GenericTag.php new file mode 100644 index 00000000..7538e812 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/GenericTag.php @@ -0,0 +1,35 @@ +object; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/NegativeBigIntegerTag.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/NegativeBigIntegerTag.php new file mode 100644 index 00000000..7dcda5d3 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/NegativeBigIntegerTag.php @@ -0,0 +1,56 @@ +object->getNormalizedData($ignoreTags); + } + + if (!$this->object instanceof ByteStringObject) { + return $this->object->getNormalizedData($ignoreTags); + } + $integer = gmp_init(bin2hex($this->object->getValue()), 16); + $minusOne = gmp_init('-1', 10); + + return gmp_strval(gmp_sub($minusOne, $integer), 10); + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/PositiveBigIntegerTag.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/PositiveBigIntegerTag.php new file mode 100644 index 00000000..35eff504 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/PositiveBigIntegerTag.php @@ -0,0 +1,54 @@ +object->getNormalizedData($ignoreTags); + } + + if (!$this->object instanceof ByteStringObject) { + return $this->object->getNormalizedData($ignoreTags); + } + + return gmp_strval(gmp_init(bin2hex($this->object->getValue()), 16), 10); + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/TagObjectManager.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/TagObjectManager.php new file mode 100644 index 00000000..b487284f --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/TagObjectManager.php @@ -0,0 +1,53 @@ +classes[$class::getTagId()] = $class; + } + + public function getClassForValue(int $value): string + { + return \array_key_exists($value, $this->classes) ? $this->classes[$value] : GenericTag::class; + } + + public function createObjectForValue(int $additionalInformation, ?string $data, CBORObject $object): TagObject + { + $value = $additionalInformation; + if ($additionalInformation >= 24) { + Assertion::string($data, 'Invalid data'); + $value = gmp_intval(gmp_init(bin2hex($data), 16)); + } + /** @var TagObject $class */ + $class = $this->getClassForValue($value); + + return $class::createFromLoadedData($additionalInformation, $data, $object); + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/TimestampTag.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/TimestampTag.php new file mode 100644 index 00000000..67592eb8 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/Tag/TimestampTag.php @@ -0,0 +1,61 @@ +object->getNormalizedData($ignoreTags); + } + switch (true) { + case $this->object instanceof UnsignedIntegerObject: + return \DateTimeImmutable::createFromFormat('U', \strval($this->object->getNormalizedData($ignoreTags))); + case $this->object instanceof HalfPrecisionFloatObject: + case $this->object instanceof SinglePrecisionFloatObject: + case $this->object instanceof DoublePrecisionFloatObject: + return \DateTimeImmutable::createFromFormat('U.u', \strval($this->object->getNormalizedData($ignoreTags))); + default: + return $this->object->getNormalizedData($ignoreTags); + } + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/TagObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/TagObject.php new file mode 100644 index 00000000..4efba2f3 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/TagObject.php @@ -0,0 +1,56 @@ +data = $data; + $this->object = $object; + } + + abstract public static function getTagId(): int; + + abstract public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): self; + + public function getValue(): CBORObject + { + return $this->object; + } + + public function __toString(): string + { + $result = parent::__toString(); + if (null !== $this->data) { + $result .= $this->data; + } + $result .= (string) $this->object; + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/TextStringObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/TextStringObject.php new file mode 100644 index 00000000..b1d36c1b --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/TextStringObject.php @@ -0,0 +1,64 @@ +data = $data; + $this->length = $length; + } + + public function getValue(): string + { + return $this->data; + } + + public function getLength(): int + { + return mb_strlen($this->data, 'utf8'); + } + + public function getNormalizedData(bool $ignoreTags = false): string + { + return $this->data; + } + + public function __toString(): string + { + $result = parent::__toString(); + if (null !== $this->length) { + $result .= $this->length; + } + $result .= $this->data; + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/TextStringWithChunkObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/TextStringWithChunkObject.php new file mode 100644 index 00000000..fb5db4f3 --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/TextStringWithChunkObject.php @@ -0,0 +1,87 @@ +data[] = $chunk; + } + + public function append(string $chunk): void + { + $this->add(new TextStringObject($chunk)); + } + + public function getValue(): string + { + $result = ''; + foreach ($this->data as $object) { + $result .= $object->getValue(); + } + + return $result; + } + + public function getLength(): int + { + $length = 0; + foreach ($this->data as $object) { + $length += $object->getLength(); + } + + return $length; + } + + public function getNormalizedData(bool $ignoreTags = false): string + { + $result = ''; + foreach ($this->data as $object) { + $result .= $object->getNormalizedData($ignoreTags); + } + + return $result; + } + + public function __toString(): string + { + $result = parent::__toString(); + foreach ($this->data as $object) { + $result .= (string) $object; + } + $bin = hex2bin('FF'); + if (false === $bin) { + throw new InvalidArgumentException('Unable to convert the data'); + } + $result .= $bin; + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/UnsignedIntegerObject.php b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/UnsignedIntegerObject.php new file mode 100644 index 00000000..324d30db --- /dev/null +++ b/lam/lib/3rdParty/composer/spomky-labs/cbor-php/src/UnsignedIntegerObject.php @@ -0,0 +1,116 @@ +data = $data; + } + + public static function createObjectForValue(int $additionalInformation, ?string $data): self + { + return new self($additionalInformation, $data); + } + + public static function create(int $value): self + { + return self::createFromGmpValue(gmp_init($value)); + } + + public static function createFromGmpValue(\GMP $value): self + { + if (gmp_cmp($value, gmp_init(0)) < 0) { + throw new InvalidArgumentException('The value must be a positive integer.'); + } + + switch (true) { + case gmp_cmp($value, gmp_init(24)) < 0: + $ai = gmp_intval($value); + $data = null; + break; + case gmp_cmp($value, gmp_init('FF', 16)) < 0: + $ai = 24; + $data = self::hex2bin(str_pad(gmp_strval($value, 16), 2, '0', STR_PAD_LEFT)); + break; + case gmp_cmp($value, gmp_init('FFFF', 16)) < 0: + $ai = 25; + $data = self::hex2bin(str_pad(gmp_strval($value, 16), 4, '0', STR_PAD_LEFT)); + break; + case gmp_cmp($value, gmp_init('FFFFFFFF', 16)) < 0: + $ai = 26; + $data = self::hex2bin(str_pad(gmp_strval($value, 16), 8, '0', STR_PAD_LEFT)); + break; + default: + throw new InvalidArgumentException('Out of range. Please use PositiveBigIntegerTag tag with ByteStringObject object instead.'); + } + + return new self($ai, $data); + } + + public function getMajorType(): int + { + return self::MAJOR_TYPE; + } + + public function getAdditionalInformation(): int + { + return $this->additionalInformation; + } + + public function getValue(): string + { + return $this->getNormalizedData(); + } + + public function getNormalizedData(bool $ignoreTags = false): string + { + if (null === $this->data) { + return \strval($this->additionalInformation); + } + + return gmp_strval(gmp_init(bin2hex($this->data), 16), 10); + } + + public function __toString(): string + { + $result = parent::__toString(); + if (null !== $this->data) { + $result .= $this->data; + } + + return $result; + } + + private static function hex2bin(string $data): string + { + $result = hex2bin($data); + if (false === $result) { + throw new InvalidArgumentException('Unable to convert the data'); + } + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-ctype/Ctype.php b/lam/lib/3rdParty/composer/symfony/polyfill-ctype/Ctype.php new file mode 100644 index 00000000..58414dc7 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-ctype/Ctype.php @@ -0,0 +1,227 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Ctype; + +/** + * Ctype implementation through regex. + * + * @internal + * + * @author Gert de Pagter + */ +final class Ctype +{ + /** + * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise. + * + * @see https://php.net/ctype-alnum + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_alnum($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is a letter, FALSE otherwise. + * + * @see https://php.net/ctype-alpha + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_alpha($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text); + } + + /** + * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise. + * + * @see https://php.net/ctype-cntrl + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_cntrl($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text); + } + + /** + * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise. + * + * @see https://php.net/ctype-digit + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_digit($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise. + * + * @see https://php.net/ctype-graph + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_graph($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text); + } + + /** + * Returns TRUE if every character in text is a lowercase letter. + * + * @see https://php.net/ctype-lower + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_lower($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text); + } + + /** + * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all. + * + * @see https://php.net/ctype-print + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_print($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text); + } + + /** + * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise. + * + * @see https://php.net/ctype-punct + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_punct($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text); + } + + /** + * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters. + * + * @see https://php.net/ctype-space + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_space($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text); + } + + /** + * Returns TRUE if every character in text is an uppercase letter. + * + * @see https://php.net/ctype-upper + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_upper($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text); + } + + /** + * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise. + * + * @see https://php.net/ctype-xdigit + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_xdigit($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text); + } + + /** + * Converts integers to their char versions according to normal ctype behaviour, if needed. + * + * If an integer between -128 and 255 inclusive is provided, + * it is interpreted as the ASCII value of a single character + * (negative values have 256 added in order to allow characters in the Extended ASCII range). + * Any other integer is interpreted as a string containing the decimal digits of the integer. + * + * @param string|int $int + * + * @return mixed + */ + private static function convert_int_to_char_for_ctype($int) + { + if (!\is_int($int)) { + return $int; + } + + if ($int < -128 || $int > 255) { + return (string) $int; + } + + if ($int < 0) { + $int += 256; + } + + return \chr($int); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-ctype/LICENSE b/lam/lib/3rdParty/composer/symfony/polyfill-ctype/LICENSE new file mode 100644 index 00000000..3f853aaf --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-ctype/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-ctype/README.md b/lam/lib/3rdParty/composer/symfony/polyfill-ctype/README.md new file mode 100644 index 00000000..8add1ab0 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-ctype/README.md @@ -0,0 +1,12 @@ +Symfony Polyfill / Ctype +======================== + +This component provides `ctype_*` functions to users who run php versions without the ctype extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-ctype/bootstrap.php b/lam/lib/3rdParty/composer/symfony/polyfill-ctype/bootstrap.php new file mode 100644 index 00000000..14d1d0fa --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-ctype/bootstrap.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (!function_exists('ctype_alnum')) { + function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } + function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } + function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } + function ctype_digit($text) { return p\Ctype::ctype_digit($text); } + function ctype_graph($text) { return p\Ctype::ctype_graph($text); } + function ctype_lower($text) { return p\Ctype::ctype_lower($text); } + function ctype_print($text) { return p\Ctype::ctype_print($text); } + function ctype_punct($text) { return p\Ctype::ctype_punct($text); } + function ctype_space($text) { return p\Ctype::ctype_space($text); } + function ctype_upper($text) { return p\Ctype::ctype_upper($text); } + function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } +} diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-ctype/composer.json b/lam/lib/3rdParty/composer/symfony/polyfill-ctype/composer.json new file mode 100644 index 00000000..090f923e --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-ctype/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/polyfill-ctype", + "type": "library", + "description": "Symfony polyfill for ctype functions", + "keywords": ["polyfill", "compatibility", "portable", "ctype"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/LICENSE b/lam/lib/3rdParty/composer/web-auth/cose-lib/LICENSE new file mode 100644 index 00000000..25cfdd66 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Spomky-Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/README.md b/lam/lib/3rdParty/composer/web-auth/cose-lib/README.md new file mode 100644 index 00000000..11297ba5 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/README.md @@ -0,0 +1,31 @@ +COSE Support for PHP +==================== + +**CBOR Object Signing and Encryption (COSE) Support for PHP** is a **PHP library** that will help you to perform cypher operations using Cose Keys. + +# Installation + +Install the library with Composer: `composer require web-auth/cose-lib`. + +# Contribution + +This repository is a sub repository of [the Web Authentication Framework](https://github.com/web-auth/webauthn-framework) project and is **READ ONLY**. + +**Please do not submit any Pull Request here.** +You should go to [the main repository](https://github.com/web-auth/webauthn-framework) instead. + +# Documentation + +The official documentation is available at https://github.com/web-auth/webauthn-framework + +# Support + +I bring solutions to your problems and answer your questions. + +If you really love that project and the work I have done or if you want I prioritize your issues, then you can help me out for a couple of :beers: or more! + +[![Become a Patreon](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/FlorentMorselli) + +# Licence + +This project is release under [MIT licence](LICENSE). diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/composer.json b/lam/lib/3rdParty/composer/web-auth/cose-lib/composer.json new file mode 100644 index 00000000..e35ff676 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/composer.json @@ -0,0 +1,45 @@ +{ + "name": "web-auth/cose-lib", + "type": "library", + "license": "MIT", + "description": "CBOR Object Signing and Encryption (COSE) For PHP", + "keywords": ["COSE", "RFC8152"], + "homepage": "https://github.com/web-auth", + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/cose/contributors" + } + ], + "require": { + "php": "^7.2", + "ext-json": "*", + "ext-openssl": "*", + "ext-mbstring": "*", + "fgrosse/phpasn1": "^2.1", + "beberlei/assert": "^3.0" + }, + "autoload": { + "psr-4": { + "Cose\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Cose\\Tests\\Unit\\": "tests/unit/" + } + }, + "extra": { + "branch-alias": { + "v1.0": "1.0.x-dev", + "v1.1": "1.1.x-dev", + "v1.2": "1.2.x-dev", + "v2.0": "2.0.x-dev", + "v2.1": "2.1.x-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Algorithm.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Algorithm.php new file mode 100644 index 00000000..ed893835 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Algorithm.php @@ -0,0 +1,19 @@ +checKey($key); + $signature = hash_hmac($this->getHashAlgorithm(), $data, $key->get(-1), true); + + return mb_substr($signature, 0, $this->getSignatureLength() / 8, '8bit'); + } + + public function verify(string $data, Key $key, string $signature): bool + { + return hash_equals($this->hash($data, $key), $signature); + } + + private function checKey(Key $key): void + { + Assertion::eq($key->type(), 4, 'Invalid key. Must be of type symmetric'); + Assertion::true($key->has(-1), 'Invalid key. The value of the key is missing'); + } + + abstract protected function getHashAlgorithm(): string; + + abstract protected function getSignatureLength(): int; +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Mac/Mac.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Mac/Mac.php new file mode 100644 index 00000000..fcf9fb51 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Mac/Mac.php @@ -0,0 +1,24 @@ +algorithms[$identifier] = $algorithm; + } + + /** + * @deprecated Will be removed in v3.0. Please use all() instead + */ + public function getAlgorithms(): iterable + { + yield from $this->algorithms; + } + + public function list(): iterable + { + yield from array_keys($this->algorithms); + } + + /** + * @return Algorithm[] + */ + public function all(): iterable + { + yield from $this->algorithms; + } + + public function has(int $identifier): bool + { + return \array_key_exists($identifier, $this->algorithms); + } + + public function get(int $identifier): Algorithm + { + Assertion::true($this->has($identifier), 'Unsupported algorithm'); + + return $this->algorithms[$identifier]; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/ManagerFactory.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/ManagerFactory.php new file mode 100644 index 00000000..f2cb15b6 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/ManagerFactory.php @@ -0,0 +1,50 @@ +algorithms[$alias] = $algorithm; + } + + public function list(): iterable + { + yield from array_keys($this->algorithms); + } + + public function all(): iterable + { + yield from array_keys($this->algorithms); + } + + public function create(array $aliases): Manager + { + $manager = new Manager(); + foreach ($aliases as $alias) { + Assertion::keyExists($this->algorithms, $alias, sprintf('The algorithm with alias "%s" is not supported', $alias)); + $manager->add($this->algorithms[$alias]); + } + + return $manager; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECDSA.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECDSA.php new file mode 100644 index 00000000..da1265b0 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECDSA.php @@ -0,0 +1,58 @@ +handleKey($key); + $result = openssl_sign($data, $signature, $key->asPEM(), $this->getHashAlgorithm()); + Assertion::true($result, 'Unable to sign the data'); + + return $signature; + } + + public function verify(string $data, Key $key, string $signature): bool + { + $key = $this->handleKey($key); + $publicKey = $key->toPublic(); + + return 1 === openssl_verify($data, $signature, $publicKey->asPEM(), $this->getHashAlgorithm()); + } + + private function handleKey(Key $key): Ec2Key + { + $key = new Ec2Key($key->getData()); + Assertion::eq($key->curve(), $this->getCurve(), 'This key cannot be used with this algorithm'); + + return $key; + } + + abstract protected function getCurve(): int; + + abstract protected function getHashAlgorithm(): int; +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECSignature.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECSignature.php new file mode 100644 index 00000000..db99c188 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECSignature.php @@ -0,0 +1,133 @@ + self::ASN1_MAX_SINGLE_BYTE ? self::ASN1_LENGTH_2BYTES : ''; + + return self::hex2bin( + self::ASN1_SEQUENCE + .$lengthPrefix.dechex($totalLength) + .self::ASN1_INTEGER.dechex($lengthR).$pointR + .self::ASN1_INTEGER.dechex($lengthS).$pointS + ); + } + + public static function fromAsn1(string $signature, int $length): string + { + $message = bin2hex($signature); + $position = 0; + + if (self::ASN1_SEQUENCE !== self::readAsn1Content($message, $position, self::BYTE_SIZE)) { + throw new InvalidArgumentException('Invalid data. Should start with a sequence.'); + } + + if (self::ASN1_LENGTH_2BYTES === self::readAsn1Content($message, $position, self::BYTE_SIZE)) { + $position += self::BYTE_SIZE; + } + + $pointR = self::retrievePositiveInteger(self::readAsn1Integer($message, $position)); + $pointS = self::retrievePositiveInteger(self::readAsn1Integer($message, $position)); + + return self::hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT).str_pad($pointS, $length, '0', STR_PAD_LEFT)); + } + + private static function octetLength(string $data): int + { + return (int) (mb_strlen($data, '8bit') / self::BYTE_SIZE); + } + + private static function preparePositiveInteger(string $data): string + { + if (mb_substr($data, 0, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT) { + return self::ASN1_NEGATIVE_INTEGER.$data; + } + + while (self::ASN1_NEGATIVE_INTEGER === mb_substr($data, 0, self::BYTE_SIZE, '8bit') + && mb_substr($data, 2, self::BYTE_SIZE, '8bit') <= self::ASN1_BIG_INTEGER_LIMIT) { + $data = mb_substr($data, 2, null, '8bit'); + } + + return $data; + } + + private static function readAsn1Content(string $message, int &$position, int $length): string + { + $content = mb_substr($message, $position, $length, '8bit'); + $position += $length; + + return $content; + } + + private static function readAsn1Integer(string $message, int &$position): string + { + if (self::ASN1_INTEGER !== self::readAsn1Content($message, $position, self::BYTE_SIZE)) { + throw new InvalidArgumentException('Invalid data. Should contain an integer.'); + } + + $length = (int) hexdec(self::readAsn1Content($message, $position, self::BYTE_SIZE)); + + return self::readAsn1Content($message, $position, $length * self::BYTE_SIZE); + } + + private static function retrievePositiveInteger(string $data): string + { + while (self::ASN1_NEGATIVE_INTEGER === mb_substr($data, 0, self::BYTE_SIZE, '8bit') + && mb_substr($data, 2, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT) { + $data = mb_substr($data, 2, null, '8bit'); + } + + return $data; + } + + private static function hex2bin(string $data): string + { + $result = \hex2bin($data); + if (false === $result) { + throw new InvalidArgumentException('Unable to convert the data'); + } + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256.php new file mode 100644 index 00000000..3378b6fd --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256.php @@ -0,0 +1,60 @@ +getSignaturePartLength()); + } + + public function verify(string $data, Key $key, string $signature): bool + { + if (mb_strlen($signature, '8bit') !== $this->getSignaturePartLength()) { + @trigger_error('Since v2.1, the method "verify" will only accept raw ECDSA signature in v3.0 and ASN.1 structures will be rejected', E_USER_DEPRECATED); + } else { + $signature = ECSignature::toAsn1($signature, $this->getSignaturePartLength()); + } + + return parent::verify($data, $key, $signature); + } + + protected function getHashAlgorithm(): int + { + return OPENSSL_ALGO_SHA256; + } + + protected function getCurve(): int + { + return Ec2Key::CURVE_P256; + } + + protected function getSignaturePartLength(): int + { + return 64; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256K.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256K.php new file mode 100644 index 00000000..206162af --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256K.php @@ -0,0 +1,60 @@ +getSignaturePartLength()); + } + + public function verify(string $data, Key $key, string $signature): bool + { + if (mb_strlen($signature, '8bit') !== $this->getSignaturePartLength()) { + @trigger_error('Since v2.1, the method "verify" will only accept raw ECDSA signature in v3.0 and ASN.1 structures will be rejected', E_USER_DEPRECATED); + } else { + $signature = ECSignature::toAsn1($signature, $this->getSignaturePartLength()); + } + + return parent::verify($data, $key, $signature); + } + + protected function getHashAlgorithm(): int + { + return OPENSSL_ALGO_SHA256; + } + + protected function getCurve(): int + { + return Ec2Key::CURVE_P256K; + } + + protected function getSignaturePartLength(): int + { + return 64; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES384.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES384.php new file mode 100644 index 00000000..b70dc313 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES384.php @@ -0,0 +1,60 @@ +getSignaturePartLength()); + } + + public function verify(string $data, Key $key, string $signature): bool + { + if (mb_strlen($signature, '8bit') !== $this->getSignaturePartLength()) { + @trigger_error('Since v2.1, the method "verify" will only accept raw ECDSA signature in v3.0 and ASN.1 structures will be rejected', E_USER_DEPRECATED); + } else { + $signature = ECSignature::toAsn1($signature, $this->getSignaturePartLength()); + } + + return parent::verify($data, $key, $signature); + } + + protected function getHashAlgorithm(): int + { + return OPENSSL_ALGO_SHA384; + } + + protected function getCurve(): int + { + return Ec2Key::CURVE_P384; + } + + protected function getSignaturePartLength(): int + { + return 96; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES512.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES512.php new file mode 100644 index 00000000..b88f2c32 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES512.php @@ -0,0 +1,60 @@ +getSignaturePartLength()); + } + + public function verify(string $data, Key $key, string $signature): bool + { + if (mb_strlen($signature, '8bit') !== $this->getSignaturePartLength()) { + @trigger_error('Since v2.1, the method "verify" accepts ASN.1 structures and raw ECDSA signature. In v3.0 and ASN.1 structures will be rejected', E_USER_DEPRECATED); + } else { + $signature = ECSignature::toAsn1($signature, $this->getSignaturePartLength()); + } + + return parent::verify($data, $key, $signature); + } + + protected function getHashAlgorithm(): int + { + return OPENSSL_ALGO_SHA512; + } + + protected function getCurve(): int + { + return Ec2Key::CURVE_P521; + } + + protected function getSignaturePartLength(): int + { + return 132; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/ED256.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/ED256.php new file mode 100644 index 00000000..a8888704 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/ED256.php @@ -0,0 +1,40 @@ +handleKey($key); + Assertion::true($key->isPrivate(), 'The key is not private'); + + $x = $key->x(); + $d = $key->d(); + $secret = $d.$x; + + switch ($key->curve()) { + case OkpKey::CURVE_ED25519: + return sodium_crypto_sign_detached($data, $secret); + default: + throw new InvalidArgumentException('Unsupported curve'); + } + } + + public function verify(string $data, Key $key, string $signature): bool + { + $key = $this->handleKey($key); + + switch ($key->curve()) { + case OkpKey::CURVE_ED25519: + return sodium_crypto_sign_verify_detached($signature, $data, $key->x()); + default: + throw new InvalidArgumentException('Unsupported curve'); + } + } + + public static function identifier(): int + { + return Algorithms::COSE_ALGORITHM_EdDSA; + } + + private function handleKey(Key $key): OkpKey + { + return new OkpKey($key->getData()); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS256.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS256.php new file mode 100644 index 00000000..cde18db6 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS256.php @@ -0,0 +1,31 @@ +handleKey($key); + $modulusLength = mb_strlen($key->n(), '8bit'); + + $em = $this->encodeEMSAPSS($data, 8 * $modulusLength - 1, $this->getHashAlgorithm()); + $message = BigInteger::createFromBinaryString($em); + $signature = $this->exponentiate($key, $message); + + return $this->convertIntegerToOctetString($signature, $modulusLength); + } + + public function verify(string $data, Key $key, string $signature): bool + { + $key = $this->handleKey($key); + $modulusLength = mb_strlen($key->n(), '8bit'); + + if (mb_strlen($signature, '8bit') !== $modulusLength) { + throw new InvalidArgumentException('Invalid modulus length'); + } + $s2 = BigInteger::createFromBinaryString($signature); + $m2 = $this->exponentiate($key, $s2); + $em = $this->convertIntegerToOctetString($m2, $modulusLength); + $modBits = 8 * $modulusLength; + + return $this->verifyEMSAPSS($data, $em, $modBits - 1, $this->getHashAlgorithm()); + } + + private function handleKey(Key $key): RsaKey + { + return new RsaKey($key->getData()); + } + + abstract protected function getHashAlgorithm(): Hash; + + private function convertIntegerToOctetString(BigInteger $x, int $xLen): string + { + $x = $x->toBytes(); + if (mb_strlen($x, '8bit') > $xLen) { + throw new RuntimeException('Unable to convert the integer'); + } + + return str_pad($x, $xLen, \chr(0), STR_PAD_LEFT); + } + + /** + * MGF1. + */ + private function getMGF1(string $mgfSeed, int $maskLen, Hash $mgfHash): string + { + $t = ''; + $count = ceil($maskLen / $mgfHash->getLength()); + for ($i = 0; $i < $count; ++$i) { + $c = pack('N', $i); + $t .= $mgfHash->hash($mgfSeed.$c); + } + + return mb_substr($t, 0, $maskLen, '8bit'); + } + + /** + * EMSA-PSS-ENCODE. + */ + private function encodeEMSAPSS(string $message, int $modulusLength, Hash $hash): string + { + $emLen = ($modulusLength + 1) >> 3; + $sLen = $hash->getLength(); + $mHash = $hash->hash($message); + if ($emLen <= $hash->getLength() + $sLen + 2) { + throw new RuntimeException(); + } + $salt = random_bytes($sLen); + $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt; + $h = $hash->hash($m2); + $ps = str_repeat(\chr(0), $emLen - $sLen - $hash->getLength() - 2); + $db = $ps.\chr(1).$salt; + $dbMask = $this->getMGF1($h, $emLen - $hash->getLength() - 1, $hash); + $maskedDB = $db ^ $dbMask; + $maskedDB[0] = ~\chr(0xFF << ($modulusLength & 7)) & $maskedDB[0]; + + return $maskedDB.$h.\chr(0xBC); + } + + /** + * EMSA-PSS-VERIFY. + */ + private function verifyEMSAPSS(string $m, string $em, int $emBits, Hash $hash): bool + { + $emLen = ($emBits + 1) >> 3; + $sLen = $hash->getLength(); + $mHash = $hash->hash($m); + if ($emLen < $hash->getLength() + $sLen + 2) { + throw new InvalidArgumentException(); + } + if ($em[mb_strlen($em, '8bit') - 1] !== \chr(0xBC)) { + throw new InvalidArgumentException(); + } + $maskedDB = mb_substr($em, 0, -$hash->getLength() - 1, '8bit'); + $h = mb_substr($em, -$hash->getLength() - 1, $hash->getLength(), '8bit'); + $temp = \chr(0xFF << ($emBits & 7)); + if ((~$maskedDB[0] & $temp) !== $temp) { + throw new InvalidArgumentException(); + } + $dbMask = $this->getMGF1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/); + $db = $maskedDB ^ $dbMask; + $db[0] = ~\chr(0xFF << ($emBits & 7)) & $db[0]; + $temp = $emLen - $hash->getLength() - $sLen - 2; + if (mb_substr($db, 0, $temp, '8bit') !== str_repeat(\chr(0), $temp)) { + throw new InvalidArgumentException(); + } + if (1 !== \ord($db[$temp])) { + throw new InvalidArgumentException(); + } + $salt = mb_substr($db, $temp + 1, null, '8bit'); // should be $sLen long + $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt; + $h2 = $hash->hash($m2); + + return hash_equals($h, $h2); + } + + /** + * Exponentiate with or without Chinese Remainder Theorem. + * Operation with primes 'p' and 'q' is appox. 2x faster. + */ + public function exponentiate(RsaKey $key, BigInteger $c): BigInteger + { + if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare(BigInteger::createFromBinaryString($key->n())) > 0) { + throw new RuntimeException(); + } + if ($key->isPublic() || !$key->hasPrimes() || !$key->hasExponents() || !$key->hasCoefficient()) { + return $c->modPow(BigInteger::createFromBinaryString($key->e()), BigInteger::createFromBinaryString($key->n())); + } + + $p = $key->primes()[0]; + $q = $key->primes()[1]; + $dP = $key->exponents()[0]; + $dQ = $key->exponents()[1]; + $qInv = BigInteger::createFromBinaryString($key->QInv()); + + $m1 = $c->modPow($dP, $p); + $m2 = $c->modPow($dQ, $q); + $h = $qInv->multiply($m1->subtract($m2)->add($p))->mod($p); + + return $m2->add($h->multiply($q)); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS1.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS1.php new file mode 100644 index 00000000..c6421d9b --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS1.php @@ -0,0 +1,29 @@ +handleKey($key); + Assertion::true($key->isPrivate(), 'The key is not private'); + + $result = openssl_sign($data, $signature, $key->asPem(), $this->getHashAlgorithm()); + Assertion::true($result, 'Unable to sign the data'); + + return $signature; + } + + public function verify(string $data, Key $key, string $signature): bool + { + $key = $this->handleKey($key); + + return 1 === openssl_verify($data, $signature, $key->asPem(), $this->getHashAlgorithm()); + } + + private function handleKey(Key $key): RsaKey + { + return new RsaKey($key->getData()); + } + + abstract protected function getHashAlgorithm(): int; +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/Signature.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/Signature.php new file mode 100644 index 00000000..d9fa0d96 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Algorithm/Signature/Signature.php @@ -0,0 +1,24 @@ + OPENSSL_ALGO_SHA256, + self::COSE_ALGORITHM_ES384 => OPENSSL_ALGO_SHA384, + self::COSE_ALGORITHM_ES512 => OPENSSL_ALGO_SHA512, + self::COSE_ALGORITHM_RS256 => OPENSSL_ALGO_SHA256, + self::COSE_ALGORITHM_RS384 => OPENSSL_ALGO_SHA384, + self::COSE_ALGORITHM_RS512 => OPENSSL_ALGO_SHA512, + self::COSE_ALGORITHM_RS1 => OPENSSL_ALGO_SHA1, + ]; + + public const COSE_HASH_MAP = [ + self::COSE_ALGORITHM_ES256K => 'sha256', + self::COSE_ALGORITHM_ES256 => 'sha256', + self::COSE_ALGORITHM_ES384 => 'sha384', + self::COSE_ALGORITHM_ES512 => 'sha512', + self::COSE_ALGORITHM_RS256 => 'sha256', + self::COSE_ALGORITHM_RS384 => 'sha384', + self::COSE_ALGORITHM_RS512 => 'sha512', + self::COSE_ALGORITHM_PS256 => 'sha256', + self::COSE_ALGORITHM_PS384 => 'sha384', + self::COSE_ALGORITHM_PS512 => 'sha512', + self::COSE_ALGORITHM_RS1 => 'sha1', + ]; + + public static function getOpensslAlgorithmFor(int $algorithmIdentifier): int + { + Assertion::keyExists(self::COSE_ALGORITHM_MAP, $algorithmIdentifier, 'The specified algorithm identifier is not supported'); + + return self::COSE_ALGORITHM_MAP[$algorithmIdentifier]; + } + + public static function getHashAlgorithmFor(int $algorithmIdentifier): string + { + Assertion::keyExists(self::COSE_HASH_MAP, $algorithmIdentifier, 'The specified algorithm identifier is not supported'); + + return self::COSE_HASH_MAP[$algorithmIdentifier]; + } + + /** + * @deprecated Will be removed in v3.0. Please use the Manager or the ManagerFactory + */ + public static function getAlgorithm(int $identifier): Algorithm + { + $algs = static::getAlgorithms(); + Assertion::keyExists($algs, $identifier, 'The specified algorithm identifier is not supported'); + + return $algs[$identifier]; + } + + /** + * @deprecated Will be removed in v3.0. Please use the Manager or the ManagerFactory + * + * @return Algorithm[] + */ + public static function getAlgorithms(): array + { + return [ + Mac\HS256::identifier() => new Mac\HS256(), + Mac\HS384::identifier() => new Mac\HS384(), + Mac\HS512::identifier() => new Mac\HS512(), + RSA\RS256::identifier() => new RSA\RS256(), + RSA\RS384::identifier() => new RSA\RS384(), + RSA\RS512::identifier() => new RSA\RS512(), + RSA\PS256::identifier() => new RSA\PS256(), + RSA\PS384::identifier() => new RSA\PS384(), + RSA\PS512::identifier() => new RSA\PS512(), + ECDSA\ES256K::identifier() => new ECDSA\ES256K(), + ECDSA\ES256::identifier() => new ECDSA\ES256(), + ECDSA\ES384::identifier() => new ECDSA\ES384(), + ECDSA\ES512::identifier() => new ECDSA\ES512(), + EdDSA\ED512::identifier() => new EdDSA\ED512(), + ]; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/Ec2Key.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/Ec2Key.php new file mode 100644 index 00000000..9db05592 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/Ec2Key.php @@ -0,0 +1,144 @@ + '1.2.840.10045.3.1.7', // NIST P-256 / secp256r1 + self::CURVE_P256K => '1.3.132.0.10', // NIST P-256K / secp256k1 + self::CURVE_P384 => '1.3.132.0.34', // NIST P-384 / secp384r1 + self::CURVE_P521 => '1.3.132.0.35', // NIST P-521 / secp521r1 + ]; + + private const CURVE_KEY_LENGTH = [ + self::CURVE_P256 => 32, + self::CURVE_P256K => 32, + self::CURVE_P384 => 48, + self::CURVE_P521 => 66, + ]; + + public function __construct(array $data) + { + parent::__construct($data); + Assertion::eq($data[self::TYPE], self::TYPE_EC2, 'Invalid EC2 key. The key type does not correspond to an EC2 key'); + Assertion::keyExists($data, self::DATA_CURVE, 'Invalid EC2 key. The curve is missing'); + Assertion::keyExists($data, self::DATA_X, 'Invalid EC2 key. The x coordinate is missing'); + Assertion::keyExists($data, self::DATA_Y, 'Invalid EC2 key. The y coordinate is missing'); + Assertion::length($data[self::DATA_X], self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]], 'Invalid length for x coordinate', null, '8bit'); + Assertion::length($data[self::DATA_Y], self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]], 'Invalid length for y coordinate', null, '8bit'); + Assertion::inArray((int) $data[self::DATA_CURVE], self::SUPPORTED_CURVES, 'The curve is not supported'); + } + + public function toPublic(): self + { + $data = $this->getData(); + unset($data[self::DATA_D]); + + return new self($data); + } + + public function x(): string + { + return $this->get(self::DATA_X); + } + + public function y(): string + { + return $this->get(self::DATA_Y); + } + + public function isPrivate(): bool + { + return \array_key_exists(self::DATA_D, $this->getData()); + } + + public function d(): string + { + Assertion::true($this->isPrivate(), 'The key is not private'); + + return $this->get(self::DATA_D); + } + + public function curve(): int + { + return (int) $this->get(self::DATA_CURVE); + } + + public function asPEM(): string + { + if ($this->isPrivate()) { + $der = new Sequence( + new Integer(1), + new OctetString(bin2hex($this->d())), + new ExplicitlyTaggedObject(0, new ObjectIdentifier($this->getCurveOid())), + new ExplicitlyTaggedObject(1, new BitString(\bin2hex($this->getUncompressedCoordinates()))) + ); + + return $this->pem('EC PRIVATE KEY', $der->getBinary()); + } + + $der = new Sequence( + new Sequence( + new ObjectIdentifier('1.2.840.10045.2.1'), + new ObjectIdentifier($this->getCurveOid()) + ), + new BitString(\bin2hex($this->getUncompressedCoordinates())) + ); + + return $this->pem('PUBLIC KEY', $der->getBinary()); + } + + private function getCurveOid(): string + { + return self::NAMED_CURVE_OID[$this->curve()]; + } + + public function getUncompressedCoordinates(): string + { + return "\x04".$this->x().$this->y(); + } + + private function pem(string $type, string $der): string + { + return sprintf("-----BEGIN %s-----\n", mb_strtoupper($type)). + chunk_split(base64_encode($der), 64, "\n"). + sprintf("-----END %s-----\n", mb_strtoupper($type)); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/Key.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/Key.php new file mode 100644 index 00000000..d7cbaa89 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/Key.php @@ -0,0 +1,90 @@ +data = $data; + } + + public static function createFromData(array $data): self + { + Assertion::keyExists($data, self::TYPE, 'Invalid key: the type is not defined'); + switch ($data[self::TYPE]) { + case 1: + return new OkpKey($data); + case 2: + return new Ec2Key($data); + case 3: + return new RsaKey($data); + case 4: + return new SymmetricKey($data); + default: + return new self($data); + } + } + + /** + * @return int|string + */ + public function type() + { + return $this->data[self::TYPE]; + } + + public function alg(): int + { + return (int) $this->get(self::ALG); + } + + public function getData(): array + { + return $this->data; + } + + public function has(int $key): bool + { + return \array_key_exists($key, $this->data); + } + + /** + * @return mixed + */ + public function get(int $key) + { + Assertion::keyExists($this->data, $key, sprintf('The key has no data at index %d', $key)); + + return $this->data[$key]; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/OkpKey.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/OkpKey.php new file mode 100644 index 00000000..2dfcc695 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/OkpKey.php @@ -0,0 +1,66 @@ +get(self::DATA_X); + } + + public function isPrivate(): bool + { + return \array_key_exists(self::DATA_D, $this->getData()); + } + + public function d(): string + { + Assertion::true($this->isPrivate(), 'The key is not private'); + + return $this->get(self::DATA_D); + } + + public function curve(): int + { + return (int) $this->get(self::DATA_CURVE); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/RsaKey.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/RsaKey.php new file mode 100644 index 00000000..dcfa39df --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/RsaKey.php @@ -0,0 +1,197 @@ +get(self::DATA_N); + } + + public function e(): string + { + return $this->get(self::DATA_E); + } + + public function d(): string + { + Assertion::true($this->isPrivate(), 'The key is not private.'); + + return $this->get(self::DATA_D); + } + + public function p(): string + { + Assertion::true($this->isPrivate(), 'The key is not private.'); + + return $this->get(self::DATA_P); + } + + public function q(): string + { + Assertion::true($this->isPrivate(), 'The key is not private.'); + + return $this->get(self::DATA_Q); + } + + public function dP(): string + { + Assertion::true($this->isPrivate(), 'The key is not private.'); + + return $this->get(self::DATA_DP); + } + + public function dQ(): string + { + Assertion::true($this->isPrivate(), 'The key is not private.'); + + return $this->get(self::DATA_DQ); + } + + public function QInv(): string + { + Assertion::true($this->isPrivate(), 'The key is not private.'); + + return $this->get(self::DATA_QI); + } + + public function other(): array + { + Assertion::true($this->isPrivate(), 'The key is not private.'); + + return $this->get(self::DATA_OTHER); + } + + public function rI(): string + { + Assertion::true($this->isPrivate(), 'The key is not private.'); + + return $this->get(self::DATA_RI); + } + + public function dI(): string + { + Assertion::true($this->isPrivate(), 'The key is not private.'); + + return $this->get(self::DATA_DI); + } + + public function tI(): string + { + Assertion::true($this->isPrivate(), 'The key is not private.'); + + return $this->get(self::DATA_TI); + } + + public function hasPrimes(): bool + { + return $this->has(self::DATA_P) && $this->has(self::DATA_Q); + } + + public function primes(): array + { + return [ + $this->p(), + $this->q(), + ]; + } + + public function hasExponents(): bool + { + return $this->has(self::DATA_DP) && $this->has(self::DATA_DQ); + } + + public function exponents(): array + { + return [ + $this->dP(), + $this->dQ(), + ]; + } + + public function hasCoefficient(): bool + { + return $this->has(self::DATA_QI); + } + + public function isPublic(): bool + { + return !$this->isPrivate(); + } + + public function isPrivate(): bool + { + return \array_key_exists(self::DATA_D, $this->getData()); + } + + public function asPem(): string + { + Assertion::false($this->isPrivate(), 'Unsupported for private keys.'); + $bitSring = new Sequence( + new Integer($this->fromBase64ToInteger($this->n())), + new Integer($this->fromBase64ToInteger($this->e())) + ); + + $der = new Sequence( + new Sequence( + new ObjectIdentifier('1.2.840.113549.1.1.1'), + new NullObject() + ), + new BitString(\bin2hex($bitSring->getBinary())) + ); + + return $this->pem('PUBLIC KEY', $der->getBinary()); + } + + private function fromBase64ToInteger(string $value): string + { + return gmp_strval(gmp_init(current(unpack('H*', $value)), 16), 10); + } + + private function pem(string $type, string $der): string + { + return sprintf("-----BEGIN %s-----\n", mb_strtoupper($type)). + chunk_split(base64_encode($der), 64, "\n"). + sprintf("-----END %s-----\n", mb_strtoupper($type)); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/SymmetricKey.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/SymmetricKey.php new file mode 100644 index 00000000..b718ea6d --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Key/SymmetricKey.php @@ -0,0 +1,33 @@ +get(self::DATA_K); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Verifier.php b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Verifier.php new file mode 100644 index 00000000..325c9bbb --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/cose-lib/src/Verifier.php @@ -0,0 +1,18 @@ +selfAttestedFRR; + } + + /** + * @return int + */ + public function getSelfAttestedFAR(): ?int + { + return $this->selfAttestedFAR; + } + + /** + * @return int|null + */ + public function getMaxTemplates(): ?int + { + return $this->maxTemplates; + } + + /** + * @return int|null + */ + public function getMaxRetries(): ?int + { + return $this->maxRetries; + } + + /** + * @return int|null + */ + public function getBlockSlowdown(): ?int + { + return $this->blockSlowdown; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + $object->selfAttestedFRR = $data['selfAttestedFRR'] ?? null; + $object->selfAttestedFAR = $data['selfAttestedFAR'] ?? null; + $object->maxTemplates = $data['maxTemplates'] ?? null; + $object->maxRetries = $data['maxRetries'] ?? null; + $object->blockSlowdown = $data['blockSlowdown'] ?? null; + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/BiometricStatusReport.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/BiometricStatusReport.php new file mode 100644 index 00000000..a37230de --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/BiometricStatusReport.php @@ -0,0 +1,101 @@ +certLevel; + } + + public function getModality(): int + { + return $this->modality; + } + + public function getEffectiveDate(): ?string + { + return $this->effectiveDate; + } + + public function getCertificationDescriptor(): ?string + { + return $this->certificationDescriptor; + } + + public function getCertificateNumber(): ?string + { + return $this->certificateNumber; + } + + public function getCertificationPolicyVersion(): ?string + { + return $this->certificationPolicyVersion; + } + + public function getCertificationRequirementsVersion(): ?string + { + return $this->certificationRequirementsVersion; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + $object->certLevel = $data['certLevel'] ?? null; + $object->modality = $data['modality'] ?? null; + $object->effectiveDate = $data['effectiveDate'] ?? null; + $object->certificationDescriptor = $data['certificationDescriptor'] ?? null; + $object->certificateNumber = $data['certificateNumber'] ?? null; + $object->certificationPolicyVersion = $data['certificationPolicyVersion'] ?? null; + $object->certificationRequirementsVersion = $data['certificationRequirementsVersion'] ?? null; + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/CodeAccuracyDescriptor.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/CodeAccuracyDescriptor.php new file mode 100644 index 00000000..990df1ff --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/CodeAccuracyDescriptor.php @@ -0,0 +1,68 @@ +base; + } + + public function getMinLength(): int + { + return $this->minLength; + } + + public function getMaxRetries(): ?int + { + return $this->maxRetries; + } + + public function getBlockSlowdown(): ?int + { + return $this->blockSlowdown; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + $object->base = $data['base'] ?? null; + $object->minLength = $data['minLength'] ?? null; + $object->maxRetries = $data['maxRetries'] ?? null; + $object->blockSlowdown = $data['blockSlowdown'] ?? null; + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/DisplayPNGCharacteristicsDescriptor.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/DisplayPNGCharacteristicsDescriptor.php new file mode 100644 index 00000000..0cca7bb8 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/DisplayPNGCharacteristicsDescriptor.php @@ -0,0 +1,124 @@ +width; + } + + public function getHeight(): int + { + return $this->height; + } + + public function getBitDepth(): int + { + return $this->bitDepth; + } + + public function getColorType(): int + { + return $this->colorType; + } + + public function getCompression(): int + { + return $this->compression; + } + + public function getFilter(): int + { + return $this->filter; + } + + public function getInterlace(): int + { + return $this->interlace; + } + + /** + * @return RgbPaletteEntry[] + */ + public function getPlte(): array + { + return $this->plte; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + $object->width = $data['width'] ?? null; + $object->compression = $data['compression'] ?? null; + $object->height = $data['height'] ?? null; + $object->bitDepth = $data['bitDepth'] ?? null; + $object->colorType = $data['colorType'] ?? null; + $object->compression = $data['compression'] ?? null; + $object->filter = $data['filter'] ?? null; + $object->interlace = $data['interlace'] ?? null; + if (isset($data['plte'])) { + $plte = $data['plte']; + Assertion::isArray($plte, 'Invalid "plte" parameter'); + foreach ($plte as $item) { + $object->plte[] = RgbPaletteEntry::createFromArray($item); + } + } + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/DistantSingleMetadata.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/DistantSingleMetadata.php new file mode 100644 index 00000000..529674a4 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/DistantSingleMetadata.php @@ -0,0 +1,60 @@ +uri = $uri; + $this->isBare64Encoded = $isBare64Encoded; + $this->httpClient = $httpClient; + $this->requestFactory = $requestFactory; + $this->additionalHeaders = $additionalHeaders; + } + + public function getMetadataStatement(): MetadataStatement + { + return MetadataStatementFetcher::fetchMetadataStatement($this->uri, $this->isBare64Encoded, $this->httpClient, $this->requestFactory, $this->additionalHeaders); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/DistantSingleMetadataFactory.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/DistantSingleMetadataFactory.php new file mode 100644 index 00000000..0120ce85 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/DistantSingleMetadataFactory.php @@ -0,0 +1,43 @@ +httpClient = $httpClient; + $this->requestFactory = $requestFactory; + } + + public function create(string $uri, bool $isBare64Encoded, array $additionalHeaders = [], ?ClientInterface $client = null): DistantSingleMetadata + { + $client = $client ?? $this->httpClient; + + return new DistantSingleMetadata($uri, $isBare64Encoded, $client, $this->requestFactory, $additionalHeaders); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/EcdaaTrustAnchor.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/EcdaaTrustAnchor.php new file mode 100644 index 00000000..891b766d --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/EcdaaTrustAnchor.php @@ -0,0 +1,90 @@ +X; + } + + public function getY(): string + { + return $this->Y; + } + + public function getC(): string + { + return $this->c; + } + + public function getSx(): string + { + return $this->sx; + } + + public function getSy(): string + { + return $this->sy; + } + + public function getG1Curve(): string + { + return $this->G1Curve; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + $object->X = $data['X'] ?? null; + $object->Y = $data['Y'] ?? null; + $object->c = $c['data'] ?? null; + $object->sx = $data['sx'] ?? null; + $object->sy = $data['sy'] ?? null; + $object->G1Curve = $data['G1Curve'] ?? null; + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/ExtensionDescriptor.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/ExtensionDescriptor.php new file mode 100644 index 00000000..6d02f3f9 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/ExtensionDescriptor.php @@ -0,0 +1,68 @@ +id; + } + + public function getTag(): ?int + { + return $this->tag; + } + + public function getData(): ?string + { + return $this->data; + } + + public function isFailIfUnknown(): bool + { + return $this->fail_if_unknown; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + $object->id = $data['id'] ?? null; + $object->tag = $data['tag'] ?? null; + $object->data = $data['data'] ?? null; + $object->fail_if_unknown = $data['fail_if_unknown'] ?? null; + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataService.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataService.php new file mode 100644 index 00000000..ce38ebb0 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataService.php @@ -0,0 +1,84 @@ +serviceUri = $serviceUri; + $this->httpClient = $httpClient; + $this->requestFactory = $requestFactory; + $this->additionalQueryStringValues = $additionalQueryStringValues; + $this->additionalHeaders = $additionalHeaders; + } + + public function getMetadataStatementFor(MetadataTOCPayloadEntry $entry): MetadataStatement + { + $uri = $this->buildUri($entry->getUrl()); + + return MetadataStatementFetcher::fetchMetadataStatement($uri, true, $this->httpClient, $this->requestFactory, $this->additionalHeaders); + } + + public function getMetadataTOCPayload(): MetadataTOCPayload + { + $uri = $this->buildUri($this->serviceUri); + + return MetadataStatementFetcher::fetchTableOfContent($uri, $this->httpClient, $this->requestFactory, $this->additionalHeaders); + } + + private function buildUri(string $uri): string + { + $parsedUri = parse($uri); + $queryString = $parsedUri['query']; + $query = parse_query($queryString ?? ''); + foreach ($this->additionalQueryStringValues as $k => $v) { + $query[$k] = $v; + } + $parsedUri['query'] = build_query($query); + + return build($parsedUri); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataServiceFactory.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataServiceFactory.php new file mode 100644 index 00000000..aa6efa0b --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataServiceFactory.php @@ -0,0 +1,43 @@ +httpClient = $httpClient; + $this->requestFactory = $requestFactory; + } + + public function create(string $serviceUri, array $additionalQueryStringValues = [], array $additionalHeaders = [], ?ClientInterface $client = null): MetadataService + { + $client = $client ?? $this->httpClient; + + return new MetadataService($serviceUri, $client, $this->requestFactory, $additionalQueryStringValues, $additionalHeaders); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataStatement.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataStatement.php new file mode 100644 index 00000000..421ae686 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataStatement.php @@ -0,0 +1,489 @@ +legalHeader; + } + + public function getAaid(): ?string + { + return $this->aaid; + } + + public function getAaguid(): ?string + { + return $this->aaguid; + } + + /** + * @return string[] + */ + public function getAttestationCertificateKeyIdentifiers(): array + { + return $this->attestationCertificateKeyIdentifiers; + } + + public function getDescription(): string + { + return $this->description; + } + + /** + * @return string[] + */ + public function getAlternativeDescriptions(): array + { + return $this->alternativeDescriptions; + } + + public function getAuthenticatorVersion(): int + { + return $this->authenticatorVersion; + } + + public function getProtocolFamily(): string + { + return $this->protocolFamily; + } + + /** + * @return Version[] + */ + public function getUpv(): array + { + return $this->upv; + } + + public function getAssertionScheme(): ?string + { + return $this->assertionScheme; + } + + public function getAuthenticationAlgorithm(): ?int + { + return $this->authenticationAlgorithm; + } + + /** + * @return int[] + */ + public function getAuthenticationAlgorithms(): array + { + return $this->authenticationAlgorithms; + } + + public function getPublicKeyAlgAndEncoding(): ?int + { + return $this->publicKeyAlgAndEncoding; + } + + /** + * @return int[] + */ + public function getPublicKeyAlgAndEncodings(): array + { + return $this->publicKeyAlgAndEncodings; + } + + /** + * @return int[] + */ + public function getAttestationTypes(): array + { + return $this->attestationTypes; + } + + /** + * @return VerificationMethodANDCombinations[] + */ + public function getUserVerificationDetails(): array + { + return $this->userVerificationDetails; + } + + public function getKeyProtection(): int + { + return $this->keyProtection; + } + + public function isKeyRestricted(): ?bool + { + return (bool) $this->isKeyRestricted; + } + + public function isFreshUserVerificationRequired(): ?bool + { + return (bool) $this->isFreshUserVerificationRequired; + } + + public function getMatcherProtection(): int + { + return $this->matcherProtection; + } + + public function getCryptoStrength(): ?int + { + return $this->cryptoStrength; + } + + public function getOperatingEnv(): ?string + { + return $this->operatingEnv; + } + + public function getAttachmentHint(): int + { + return $this->attachmentHint; + } + + public function isSecondFactorOnly(): ?bool + { + return (bool) $this->isSecondFactorOnly; + } + + public function getTcDisplay(): int + { + return $this->tcDisplay; + } + + public function getTcDisplayContentType(): ?string + { + return $this->tcDisplayContentType; + } + + /** + * @return DisplayPNGCharacteristicsDescriptor[] + */ + public function getTcDisplayPNGCharacteristics(): array + { + return $this->tcDisplayPNGCharacteristics; + } + + /** + * @return string[] + */ + public function getAttestationRootCertificates(): array + { + return $this->attestationRootCertificates; + } + + /** + * @return EcdaaTrustAnchor[] + */ + public function getEcdaaTrustAnchors(): array + { + return $this->ecdaaTrustAnchors; + } + + public function getIcon(): ?string + { + return $this->icon; + } + + /** + * @return ExtensionDescriptor[] + */ + public function getSupportedExtensions(): array + { + return $this->supportedExtensions; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + foreach (['description', 'protocolFamily'] as $key) { + if (!isset($data[$key])) { + throw new InvalidArgumentException(sprintf('The parameter "%s" is missing', $key)); + } + } + $object->legalHeader = $data['legalHeader'] ?? null; + $object->aaid = $data['aaid'] ?? null; + $object->aaguid = $data['aaguid'] ?? null; + $object->attestationCertificateKeyIdentifiers = $data['attestationCertificateKeyIdentifiers'] ?? []; + $object->description = $data['description']; + $object->alternativeDescriptions = $data['alternativeDescriptions'] ?? []; + $object->authenticatorVersion = $data['authenticatorVersion'] ?? 0; + $object->protocolFamily = $data['protocolFamily']; + if (isset($data['upv'])) { + $upv = $data['upv']; + Assertion::isArray($upv, 'Invalid Metadata Statement'); + foreach ($upv as $value) { + Assertion::isArray($value, 'Invalid Metadata Statement'); + $object->upv[] = Version::createFromArray($value); + } + } + $object->assertionScheme = $data['assertionScheme'] ?? null; + $object->authenticationAlgorithm = $data['authenticationAlgorithm'] ?? null; + $object->authenticationAlgorithms = $data['authenticationAlgorithms'] ?? []; + $object->publicKeyAlgAndEncoding = $data['publicKeyAlgAndEncoding'] ?? null; + $object->publicKeyAlgAndEncodings = $data['publicKeyAlgAndEncodings'] ?? []; + $object->attestationTypes = $data['attestationTypes'] ?? []; + if (isset($data['userVerificationDetails'])) { + $userVerificationDetails = $data['userVerificationDetails']; + Assertion::isArray($userVerificationDetails, 'Invalid Metadata Statement'); + foreach ($userVerificationDetails as $value) { + Assertion::isArray($value, 'Invalid Metadata Statement'); + $object->userVerificationDetails[] = VerificationMethodANDCombinations::createFromArray($value); + } + } + $object->keyProtection = $data['keyProtection'] ?? 0; + $object->isKeyRestricted = $data['isKeyRestricted'] ?? null; + $object->isFreshUserVerificationRequired = $data['isFreshUserVerificationRequired'] ?? null; + $object->matcherProtection = $data['matcherProtection'] ?? 0; + $object->cryptoStrength = $data['cryptoStrength'] ?? null; + $object->operatingEnv = $data['operatingEnv'] ?? null; + $object->attachmentHint = $data['attachmentHint'] ?? 0; + $object->isSecondFactorOnly = $data['isSecondFactorOnly'] ?? null; + $object->tcDisplay = $data['tcDisplay'] ?? 0; + $object->tcDisplayContentType = $data['tcDisplayContentType'] ?? null; + if (isset($data['tcDisplayPNGCharacteristics'])) { + $tcDisplayPNGCharacteristics = $data['tcDisplayPNGCharacteristics']; + Assertion::isArray($tcDisplayPNGCharacteristics, 'Invalid Metadata Statement'); + foreach ($tcDisplayPNGCharacteristics as $tcDisplayPNGCharacteristic) { + Assertion::isArray($tcDisplayPNGCharacteristic, 'Invalid Metadata Statement'); + $object->tcDisplayPNGCharacteristics[] = DisplayPNGCharacteristicsDescriptor::createFromArray($tcDisplayPNGCharacteristic); + } + } + $object->attestationRootCertificates = $data['attestationRootCertificates'] ?? []; + $object->ecdaaTrustAnchors = $data['ecdaaTrustAnchors'] ?? []; + $object->icon = $data['icon'] ?? null; + if (isset($data['supportedExtensions'])) { + $supportedExtensions = $data['supportedExtensions']; + Assertion::isArray($supportedExtensions, 'Invalid Metadata Statement'); + foreach ($supportedExtensions as $supportedExtension) { + Assertion::isArray($supportedExtension, 'Invalid Metadata Statement'); + $object->supportedExtensions[] = ExtensionDescriptor::createFromArray($supportedExtension); + } + } + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataStatementFetcher.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataStatementFetcher.php new file mode 100644 index 00000000..7f6b7eb0 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataStatementFetcher.php @@ -0,0 +1,79 @@ +createRequest('GET', $uri); + foreach ($additionalHeaders as $k => $v) { + $request = $request->withHeader($k, $v); + } + $response = $client->sendRequest($request); + Assertion::eq(200, $response->getStatusCode(), sprintf('Unable to contact the server. Response code is %d', $response->getStatusCode())); + $content = $response->getBody()->getContents(); + Assertion::notEmpty($content, 'Unable to contact the server. The response has no content'); + + return $content; + } + + private static function getJwsPayload(string $token): string + { + $jws = (new CompactSerializer())->unserialize($token); + Assertion::eq(1, $jws->countSignatures(), 'Invalid response from the metadata service. Only one signature shall be present.'); + $signature = $jws->getSignature(0); + $payload = $jws->getPayload(); + Assertion::notEmpty($payload, 'Invalid response from the metadata service. The token payload is empty.'); + $header = $signature->getProtectedHeader(); + Assertion::keyExists($header, 'alg', 'The "alg" parameter is missing.'); + Assertion::eq($header['alg'], 'ES256', 'The expected "alg" parameter value should be "ES256".'); + Assertion::keyExists($header, 'x5c', 'The "x5c" parameter is missing.'); + Assertion::isArray($header['x5c'], 'The "x5c" parameter should be an array.'); + $key = JWKFactory::createFromX5C($header['x5c']); + $algorithm = new ES256(); + $isValid = $algorithm->verify($key, $signature->getEncodedProtectedHeader().'.'.$jws->getEncodedPayload(), $signature->getSignature()); + Assertion::true($isValid, 'Invalid response from the metadata service. The token signature is invalid.'); + + return $jws->getPayload(); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataStatementRepository.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataStatementRepository.php new file mode 100644 index 00000000..2743e184 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataStatementRepository.php @@ -0,0 +1,19 @@ +legalHeader; + } + + public function getNo(): int + { + return $this->no; + } + + public function getNextUpdate(): string + { + return $this->nextUpdate; + } + + /** + * @return MetadataTOCPayloadEntry[] + */ + public function getEntries(): array + { + return $this->entries; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + $object->legalHeader = $data['legalHeader'] ?? null; + $object->nextUpdate = $data['nextUpdate'] ?? null; + $object->no = $data['no'] ?? null; + $object->entries = []; + if (isset($data['entries'])) { + foreach ($data['entries'] as $k => $entry) { + $object->entries[$k] = MetadataTOCPayloadEntry::createFromArray($entry); + } + } + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataTOCPayloadEntry.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataTOCPayloadEntry.php new file mode 100644 index 00000000..a1320115 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/MetadataTOCPayloadEntry.php @@ -0,0 +1,145 @@ +aaid; + } + + public function getAaguid(): ?string + { + return $this->aaguid; + } + + public function getAttestationCertificateKeyIdentifiers(): array + { + return $this->attestationCertificateKeyIdentifiers; + } + + public function getHash(): ?string + { + return $this->hash; + } + + public function getUrl(): ?string + { + return $this->url; + } + + public function getBiometricStatusReports(): array + { + return $this->biometricStatusReports; + } + + /** + * @return StatusReport[] + */ + public function getStatusReports(): array + { + return $this->statusReports; + } + + public function getTimeOfLastStatusChange(): string + { + return $this->timeOfLastStatusChange; + } + + public function getRogueListURL(): string + { + return $this->rogueListURL; + } + + public function getRogueListHash(): string + { + return $this->rogueListHash; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + $object->aaid = $data['aaid'] ?? null; + $object->aaguid = $data['aaguid'] ?? null; + $object->attestationCertificateKeyIdentifiers = $data['attestationCertificateKeyIdentifiers'] ?? null; + $object->hash = $data['hash'] ?? null; + $object->url = $data['url'] ?? null; + $object->biometricStatusReports = isset($data['biometricStatusReports']) ? BiometricStatusReport::createFromArray($data['biometricStatusReports']) : null; + $object->statusReports = []; + if (isset($data['statusReports'])) { + Assertion::isArray($data['statusReports'], 'Invalid status report'); + foreach ($data['statusReports'] as $k => $statusReport) { + $object->statusReports[$k] = StatusReport::createFromArray($statusReport); + } + } + $object->timeOfLastStatusChange = $data['timeOfLastStatusChange'] ?? null; + $object->rogueListURL = $data['rogueListURL'] ?? null; + $object->rogueListHash = $data['rogueListHash'] ?? null; + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/PatternAccuracyDescriptor.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/PatternAccuracyDescriptor.php new file mode 100644 index 00000000..75352e1f --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/PatternAccuracyDescriptor.php @@ -0,0 +1,57 @@ +minComplexity; + } + + public function getMaxRetries(): ?int + { + return $this->maxRetries; + } + + public function getBlockSlowdown(): ?int + { + return $this->blockSlowdown; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + $object->minComplexity = $data['minComplexity'] ?? null; + $object->maxRetries = $data['maxRetries'] ?? null; + $object->blockSlowdown = $data['blockSlowdown'] ?? null; + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/RgbPaletteEntry.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/RgbPaletteEntry.php new file mode 100644 index 00000000..49f9fa20 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/RgbPaletteEntry.php @@ -0,0 +1,57 @@ +r; + } + + public function getG(): int + { + return $this->g; + } + + public function getB(): int + { + return $this->b; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + $object->r = $data['r'] ?? null; + $object->g = $data['g'] ?? null; + $object->b = $data['b'] ?? null; + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/RogueListEntry.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/RogueListEntry.php new file mode 100644 index 00000000..fc5fa5a5 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/RogueListEntry.php @@ -0,0 +1,45 @@ +sk; + } + + public function getDate(): string + { + return $this->date; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + $object->sk = $data['sk'] ?? null; + $object->date = $data['date'] ?? null; + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/SimpleMetadataStatementRepository.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/SimpleMetadataStatementRepository.php new file mode 100644 index 00000000..621a9d47 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/SimpleMetadataStatementRepository.php @@ -0,0 +1,138 @@ +cacheItemPool = $cacheItemPool; + } + + public function addService(string $name, MetadataService $service): void + { + $this->services[$name] = $service; + } + + public function addSingleStatement(string $name, SingleMetadata $singleStatements): void + { + $this->singleStatements[$name] = $singleStatements; + } + + public function findOneByAAGUID(string $aaguid): ?MetadataStatement + { + $metadataStatement = $this->findOneByAAGUIDFromServices($aaguid); + if (null !== $metadataStatement) { + return $metadataStatement; + } + + return $this->findOneByAAGUIDFromSingleStatements($aaguid); + } + + private function findOneByAAGUIDFromSingleStatements(string $aaguid): ?MetadataStatement + { + foreach ($this->singleStatements as $name => $singleStatement) { + try { + $singleCacheItem = $this->cacheItemPool->getItem(sprintf('MDS-%s', $name)); + if (!$singleCacheItem->isHit()) { + $metadataStatement = $singleStatement->getMetadataStatement(); + $singleCacheItem->set($metadataStatement); + $this->cacheItemPool->save($singleCacheItem); + } else { + $metadataStatement = $singleCacheItem->get(); + } + + if ($metadataStatement->getAaguid() === $aaguid) { + return $metadataStatement; + } + } catch (Throwable $throwable) { + continue; + } + } + + return null; + } + + private function findOneByAAGUIDFromServices(string $aaguid): ?MetadataStatement + { + foreach ($this->services as $name => $service) { + try { + $tocCacheItem = $this->cacheItemPool->getItem(sprintf('TOC-%s', $name)); + if (!$tocCacheItem->isHit()) { + $tableOfContent = $service->getMetadataTOCPayload(); + $tocCacheItem->set($tableOfContent); + $this->cacheItemPool->save($tocCacheItem); + $needCacheUpdate = true; + } else { + $tableOfContent = $tocCacheItem->get(); + $nextUpdate = DateTimeImmutable::createFromFormat('Y-m-d', $tableOfContent->getNextUpdate()); + if (false === $nextUpdate) { + $needCacheUpdate = true; + } else { + $needCacheUpdate = $nextUpdate->getTimestamp() < time(); + if ($needCacheUpdate) { + $tableOfContent = $service->getMetadataTOCPayload(); + $tocCacheItem->set($tableOfContent); + $this->cacheItemPool->save($tocCacheItem); + } + } + } + } catch (Throwable $throwable) { + continue; + } + foreach ($tableOfContent->getEntries() as $entry) { + $url = $entry->getUrl(); + if (null === $url) { + continue; + } + try { + $mdsCacheItem = $this->cacheItemPool->getItem(sprintf('MDS-%s', urlencode($url))); + if ($mdsCacheItem->isHit() && !$needCacheUpdate) { + $metadataStatement = $mdsCacheItem->get(); + } else { + $metadataStatement = $service->getMetadataStatementFor($entry); + $mdsCacheItem->set($metadataStatement); + $this->cacheItemPool->save($mdsCacheItem); + } + if ($metadataStatement->getAaguid() === $aaguid) { + return $metadataStatement; + } + } catch (Throwable $throwable) { + continue; + } + } + } + + return null; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/SingleMetadata.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/SingleMetadata.php new file mode 100644 index 00000000..86c35ef9 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/SingleMetadata.php @@ -0,0 +1,54 @@ +data = $data; + $this->isBare64Encoded = $isBare64Encoded; + } + + public function getMetadataStatement(): MetadataStatement + { + if (null === $this->statement) { + $json = $this->data; + if ($this->isBare64Encoded) { + $json = base64_decode($this->data, true); + Assertion::string($json, 'Unable to decode the data'); + } + $statement = json_decode($json, true); + Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Unable to decode the data'); + $this->statement = MetadataStatement::createFromArray($statement); + } + + return $this->statement; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/StatusReport.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/StatusReport.php new file mode 100644 index 00000000..29a82048 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/StatusReport.php @@ -0,0 +1,114 @@ +status; + } + + public function getEffectiveDate(): ?string + { + return $this->effectiveDate; + } + + public function getCertificate(): ?string + { + return $this->certificate; + } + + public function getUrl(): ?string + { + return $this->url; + } + + public function getCertificationDescriptor(): ?string + { + return $this->certificationDescriptor; + } + + public function getCertificateNumber(): ?string + { + return $this->certificateNumber; + } + + public function getCertificationPolicyVersion(): ?string + { + return $this->certificationPolicyVersion; + } + + public function getCertificationRequirementsVersion(): ?string + { + return $this->certificationRequirementsVersion; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + $object->status = $data['status'] ?? null; + $object->effectiveDate = $data['effectiveDate'] ?? null; + $object->certificate = $data['certificate'] ?? null; + $object->url = $data['url'] ?? null; + $object->certificationDescriptor = $data['certificationDescriptor'] ?? null; + $object->certificateNumber = $data['certificateNumber'] ?? null; + $object->certificationPolicyVersion = $data['certificationPolicyVersion'] ?? null; + $object->certificationRequirementsVersion = $data['certificationRequirementsVersion'] ?? null; + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/VerificationMethodANDCombinations.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/VerificationMethodANDCombinations.php new file mode 100644 index 00000000..acba589a --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/VerificationMethodANDCombinations.php @@ -0,0 +1,44 @@ +verificationMethods; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + + foreach ($data as $datum) { + Assertion::isArray($datum, 'Invalid verificationMethod and combinations'); + $object->verificationMethods[] = VerificationMethodDescriptor::createFromArray($datum); + } + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/VerificationMethodDescriptor.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/VerificationMethodDescriptor.php new file mode 100644 index 00000000..c397a9ab --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/VerificationMethodDescriptor.php @@ -0,0 +1,80 @@ +userVerification; + } + + public function getCaDesc(): ?CodeAccuracyDescriptor + { + return $this->caDesc; + } + + public function getBaDesc(): ?BiometricAccuracyDescriptor + { + return $this->baDesc; + } + + public function getPaDesc(): ?PatternAccuracyDescriptor + { + return $this->paDesc; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + $object->userVerification = $data['userVerification'] ?? null; + $object->caDesc = isset($data['caDesc']) ? CodeAccuracyDescriptor::createFromArray($data['caDesc']) : null; + $object->baDesc = isset($data['baDesc']) ? BiometricAccuracyDescriptor::createFromArray($data['baDesc']) : null; + $object->paDesc = isset($data['paDesc']) ? PatternAccuracyDescriptor::createFromArray($data['paDesc']) : null; + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/metadata-service/src/Version.php b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/Version.php new file mode 100644 index 00000000..64bb4380 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/metadata-service/src/Version.php @@ -0,0 +1,46 @@ +major; + } + + public function getMinor(): int + { + return $this->minor; + } + + public static function createFromArray(array $data): self + { + $object = new self(); + $object->major = $data['major'] ?? null; + $object->minor = $data['minor'] ?? null; + + return $object; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/LICENSE b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/LICENSE new file mode 100644 index 00000000..25cfdd66 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Spomky-Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/README.md b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/README.md new file mode 100644 index 00000000..c3ee5e8d --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/README.md @@ -0,0 +1,31 @@ +FIDO2/Webauthn Support for PHP +============================== + +**FIDO2/Webauthn Support for PHP** is a **PHP library** that will help you to support compatible security tokens and devices. + +# Installation + +Install the library with Composer: `composer require web-auth/webauthn-lib`. + +# Contribution + +This repository is a sub repository of [the Web Authentication Framework](https://github.com/web-auth/webauthn-framework) project and is **READ ONLY**. + +**Please do not submit any Pull Request here.** +You should go to [the main repository](https://github.com/web-auth/webauthn-framework) instead. + +# Documentation + +The official documentation is available at https://github.com/web-auth/webauthn-framework + +# Support + +I bring solutions to your problems and answer your questions. + +If you really love that project and the work I have done or if you want I prioritize your issues, then you can help me out for a couple of :beers: or more! + +[![Become a Patreon](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/FlorentMorselli) + +# Licence + +This project is release under [MIT licence](LICENSE). diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/composer.json b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/composer.json new file mode 100644 index 00000000..91b51164 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/composer.json @@ -0,0 +1,59 @@ +{ + "name": "web-auth/webauthn-lib", + "type": "library", + "license": "MIT", + "description": "FIDO2/Webauthn Support For PHP", + "keywords": ["FIDO", "FIDO2", "webauthn"], + "homepage": "https://github.com/web-auth", + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/webauthn-library/contributors" + } + ], + "require": { + "php": "^7.2", + "ext-json": "*", + "ext-openssl": "*", + "ext-mbstring": "*", + "nyholm/psr7": "^1.1", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ramsey/uuid": "^3.8", + "spomky-labs/base64url": "^2.0", + "spomky-labs/cbor-php": "^1.0.2", + "beberlei/assert": "^3.0", + "web-auth/cose-lib": "self.version", + "web-auth/metadata-service": "self.version" + }, + "autoload": { + "psr-4": { + "Webauthn\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Webauthn\\Tests\\Functional\\": "tests/functional/", + "Webauthn\\Tests\\Unit\\": "tests/unit/" + } + }, + "suggest": { + "web-token/jwt-signature-algorithm-rsa": "Mandatory for the AndroidSafetyNet Attestation Statement support", + "web-token/jwt-signature-algorithm-ecdsa": "Recommended for the AndroidSafetyNet Attestation Statement support", + "web-token/jwt-signature-algorithm-eddsa": "Recommended for the AndroidSafetyNet Attestation Statement support" + }, + "extra": { + "branch-alias": { + "v1.0": "1.0.x-dev", + "v1.1": "1.1.x-dev", + "v1.2": "1.2.x-dev", + "v2.0": "2.0.x-dev", + "v2.1": "2.1.x-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AndroidKeyAttestationStatementSupport.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AndroidKeyAttestationStatementSupport.php new file mode 100644 index 00000000..c66876f1 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AndroidKeyAttestationStatementSupport.php @@ -0,0 +1,161 @@ +decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); + $this->metadataStatementRepository = $metadataStatementRepository; + } + + public function name(): string + { + return 'android-key'; + } + + public function load(array $attestation): AttestationStatement + { + Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); + foreach (['sig', 'x5c', 'alg'] as $key) { + Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); + } + $certificates = $attestation['attStmt']['x5c']; + Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.'); + Assertion::greaterThan(\count($certificates), 0, 'The attestation statement value "x5c" must be a list with at least one certificate.'); + Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.'); + $certificates = CertificateToolbox::convertAllDERToPEM($certificates); + + return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates)); + } + + public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool + { + $trustPath = $attestationStatement->getTrustPath(); + Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); + + $certificates = $trustPath->getCertificates(); + if (null !== $this->metadataStatementRepository) { + $certificates = CertificateToolbox::checkAttestationMedata( + $attestationStatement, + $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), + $certificates, + $this->metadataStatementRepository + ); + } + + //Decode leaf attestation certificate + $leaf = $certificates[0]; + $this->checkCertificateAndGetPublicKey($leaf, $clientDataJSONHash, $authenticatorData); + + $signedData = $authenticatorData->getAuthData().$clientDataJSONHash; + $alg = $attestationStatement->get('alg'); + + return 1 === openssl_verify($signedData, $attestationStatement->get('sig'), $leaf, Algorithms::getOpensslAlgorithmFor((int) $alg)); + } + + private function checkCertificateAndGetPublicKey(string $certificate, string $clientDataHash, AuthenticatorData $authenticatorData): void + { + $resource = openssl_pkey_get_public($certificate); + Assertion::isResource($resource, 'Unable to read the certificate'); + $details = openssl_pkey_get_details($resource); + Assertion::isArray($details, 'Unable to read the certificate'); + + //Check that authData publicKey matches the public key in the attestation certificate + $attestedCredentialData = $authenticatorData->getAttestedCredentialData(); + Assertion::notNull($attestedCredentialData, 'No attested credential data found'); + $publicKeyData = $attestedCredentialData->getCredentialPublicKey(); + Assertion::notNull($publicKeyData, 'No attested public key found'); + $publicDataStream = new StringStream($publicKeyData); + $coseKey = $this->decoder->decode($publicDataStream)->getNormalizedData(false); + Assertion::true($publicDataStream->isEOF(), 'Invalid public key data. Presence of extra bytes.'); + $publicDataStream->close(); + $publicKey = Key::createFromData($coseKey); + + Assertion::true(($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey), 'Unsupported key type'); + Assertion::eq($publicKey->asPEM(), $details['key'], 'Invalid key'); + + /*---------------------------*/ + $certDetails = openssl_x509_parse($certificate); + + //Find Android KeyStore Extension with OID “1.3.6.1.4.1.11129.2.1.17” in certificate extensions + Assertion::keyExists($certDetails, 'extensions', 'The certificate has no extension'); + Assertion::isArray($certDetails['extensions'], 'The certificate has no extension'); + Assertion::keyExists($certDetails['extensions'], '1.3.6.1.4.1.11129.2.1.17', 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is missing'); + $extension = $certDetails['extensions']['1.3.6.1.4.1.11129.2.1.17']; + $extensionAsAsn1 = ASNObject::fromBinary($extension); + Assertion::isInstanceOf($extensionAsAsn1, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); + $objects = $extensionAsAsn1->getChildren(); + + //Check that attestationChallenge is set to the clientDataHash. + Assertion::keyExists($objects, 4, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); + Assertion::isInstanceOf($objects[4], OctetString::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); + Assertion::eq($clientDataHash, hex2bin(($objects[4])->getContent()), 'The client data hash is not valid'); + + //Check that both teeEnforced and softwareEnforced structures don’t contain allApplications(600) tag. + Assertion::keyExists($objects, 6, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); + $softwareEnforcedFlags = $objects[6]; + Assertion::isInstanceOf($softwareEnforcedFlags, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); + $this->checkAbsenceOfAllApplicationsTag($softwareEnforcedFlags); + + Assertion::keyExists($objects, 7, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); + $teeEnforcedFlags = $objects[6]; + Assertion::isInstanceOf($teeEnforcedFlags, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); + $this->checkAbsenceOfAllApplicationsTag($teeEnforcedFlags); + } + + private function checkAbsenceOfAllApplicationsTag(Sequence $sequence): void + { + foreach ($sequence->getChildren() as $tag) { + Assertion::isInstanceOf($tag, ExplicitlyTaggedObject::class, 'Invalid tag'); + /* @var ExplicitlyTaggedObject $tag */ + Assertion::notEq(600, (int) $tag->getTag(), 'Forbidden tag 600 found'); + } + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AndroidSafetyNetAttestationStatementSupport.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AndroidSafetyNetAttestationStatementSupport.php new file mode 100644 index 00000000..bc0d5ee0 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AndroidSafetyNetAttestationStatementSupport.php @@ -0,0 +1,252 @@ +jwsSerializer = new CompactSerializer(); + $this->apiKey = $apiKey; + $this->client = $client; + $this->requestFactory = $requestFactory; + $this->initJwsVerifier(); + $this->leeway = $leeway; + $this->maxAge = $maxAge; + $this->metadataStatementRepository = $metadataStatementRepository; + } + + public function name(): string + { + return 'android-safetynet'; + } + + public function load(array $attestation): AttestationStatement + { + Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); + foreach (['ver', 'response'] as $key) { + Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); + Assertion::notEmpty($attestation['attStmt'][$key], sprintf('The attestation statement value "%s" is empty.', $key)); + } + $jws = $this->jwsSerializer->unserialize($attestation['attStmt']['response']); + $jwsHeader = $jws->getSignature(0)->getProtectedHeader(); + Assertion::keyExists($jwsHeader, 'x5c', 'The response in the attestation statement must contain a "x5c" header.'); + Assertion::notEmpty($jwsHeader['x5c'], 'The "x5c" parameter in the attestation statement response must contain at least one certificate.'); + $certificates = $this->convertCertificatesToPem($jwsHeader['x5c']); + $attestation['attStmt']['jws'] = $jws; + + return AttestationStatement::createBasic( + $this->name(), + $attestation['attStmt'], + new CertificateTrustPath($certificates) + ); + } + + public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool + { + $trustPath = $attestationStatement->getTrustPath(); + Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); + $certificates = $trustPath->getCertificates(); + if (null !== $this->metadataStatementRepository) { + $certificates = CertificateToolbox::checkAttestationMedata( + $attestationStatement, + $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), + $certificates, + $this->metadataStatementRepository + ); + } + + $parsedCertificate = openssl_x509_parse(current($certificates)); + Assertion::isArray($parsedCertificate, 'Invalid attestation object'); + Assertion::keyExists($parsedCertificate, 'subject', 'Invalid attestation object'); + Assertion::keyExists($parsedCertificate['subject'], 'CN', 'Invalid attestation object'); + Assertion::eq($parsedCertificate['subject']['CN'], 'attest.android.com', 'Invalid attestation object'); + + /** @var JWS $jws */ + $jws = $attestationStatement->get('jws'); + $payload = $jws->getPayload(); + $this->validatePayload($payload, $clientDataJSONHash, $authenticatorData); + + //Check the signature + $this->validateSignature($jws, $trustPath); + + //Check against Google service + $this->validateUsingGoogleApi($attestationStatement); + + return true; + } + + private function validatePayload(?string $payload, string $clientDataJSONHash, AuthenticatorData $authenticatorData): void + { + Assertion::notNull($payload, 'Invalid attestation object'); + $payload = JsonConverter::decode($payload); + Assertion::isArray($payload, 'Invalid attestation object'); + Assertion::keyExists($payload, 'nonce', 'Invalid attestation object. "nonce" is missing.'); + Assertion::eq($payload['nonce'], base64_encode(hash('sha256', $authenticatorData->getAuthData().$clientDataJSONHash, true)), 'Invalid attestation object. Invalid nonce'); + Assertion::keyExists($payload, 'ctsProfileMatch', 'Invalid attestation object. "ctsProfileMatch" is missing.'); + Assertion::true($payload['ctsProfileMatch'], 'Invalid attestation object. "ctsProfileMatch" value is false.'); + Assertion::keyExists($payload, 'timestampMs', 'Invalid attestation object. Timestamp is missing.'); + Assertion::integer($payload['timestampMs'], 'Invalid attestation object. Timestamp shall be an integer.'); + $currentTime = time() * 1000; + Assertion::lessOrEqualThan($payload['timestampMs'], $currentTime + $this->leeway, sprintf('Invalid attestation object. Issued in the future. Current time: %d. Response time: %d', $currentTime, $payload['timestampMs'])); + Assertion::lessOrEqualThan($currentTime - $payload['timestampMs'], $this->maxAge, sprintf('Invalid attestation object. Too old. Current time: %d. Response time: %d', $currentTime, $payload['timestampMs'])); + } + + private function validateSignature(JWS $jws, CertificateTrustPath $trustPath): void + { + $jwk = JWKFactory::createFromCertificate($trustPath->getCertificates()[0]); + $isValid = $this->jwsVerifier->verifyWithKey($jws, $jwk, 0); + Assertion::true($isValid, 'Invalid response signature'); + } + + private function validateUsingGoogleApi(AttestationStatement $attestationStatement): void + { + if (null === $this->client || null === $this->apiKey || null === $this->requestFactory) { + return; + } + $uri = sprintf('https://www.googleapis.com/androidcheck/v1/attestations/verify?key=%s', urlencode($this->apiKey)); + $requestBody = sprintf('{"signedAttestation":"%s"}', $attestationStatement->get('response')); + $request = $this->requestFactory->createRequest('POST', $uri); + $request = $request->withHeader('content-type', 'application/json'); + $request->getBody()->write($requestBody); + + $response = $this->client->sendRequest($request); + $this->checkGoogleApiResponse($response); + $responseBody = $this->getResponseBody($response); + $responseBodyJson = json_decode($responseBody, true); + Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid response.'); + Assertion::keyExists($responseBodyJson, 'isValidSignature', 'Invalid response.'); + Assertion::boolean($responseBodyJson['isValidSignature'], 'Invalid response.'); + Assertion::true($responseBodyJson['isValidSignature'], 'Invalid response.'); + } + + private function getResponseBody(ResponseInterface $response): string + { + $responseBody = ''; + $response->getBody()->rewind(); + do { + $tmp = $response->getBody()->read(1024); + if ('' === $tmp) { + break; + } + $responseBody .= $tmp; + } while (true); + + return $responseBody; + } + + private function checkGoogleApiResponse(ResponseInterface $response): void + { + Assertion::eq(200, $response->getStatusCode(), 'Request did not succeeded'); + Assertion::true($response->hasHeader('content-type'), 'Unrecognized response'); + + foreach ($response->getHeader('content-type') as $header) { + if (0 === mb_strpos($header, 'application/json')) { + return; + } + } + + throw new InvalidArgumentException('Unrecognized response'); + } + + private function convertCertificatesToPem(array $certificates): array + { + foreach ($certificates as $k => $v) { + $certificates[$k] = CertificateToolbox::fixPEMStructure($v); + } + + return $certificates; + } + + private function initJwsVerifier(): void + { + $algorithmClasses = [ + Algorithm\RS256::class, Algorithm\RS384::class, Algorithm\RS512::class, + Algorithm\PS256::class, Algorithm\PS384::class, Algorithm\PS512::class, + Algorithm\ES256::class, Algorithm\ES384::class, Algorithm\ES512::class, + Algorithm\EdDSA::class, + ]; + $algorithms = []; + foreach ($algorithmClasses as $key => $algorithm) { + if (class_exists($algorithm)) { + $algorithms[] = new $algorithm(); + } + } + $algorithmManager = new AlgorithmManager($algorithms); + $this->jwsVerifier = new JWSVerifier($algorithmManager); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationObject.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationObject.php new file mode 100644 index 00000000..5d8d5464 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationObject.php @@ -0,0 +1,54 @@ +rawAttestationObject = $rawAttestationObject; + $this->attStmt = $attStmt; + $this->authData = $authData; + } + + public function getRawAttestationObject(): string + { + return $this->rawAttestationObject; + } + + public function getAttStmt(): AttestationStatement + { + return $this->attStmt; + } + + public function getAuthData(): AuthenticatorData + { + return $this->authData; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationObjectLoader.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationObjectLoader.php new file mode 100644 index 00000000..c9d4c939 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationObjectLoader.php @@ -0,0 +1,98 @@ +decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); + $this->attestationStatementSupportManager = $attestationStatementSupportManager; + } + + public function load(string $data): AttestationObject + { + $decodedData = Base64Url::decode($data); + $stream = new StringStream($decodedData); + $parsed = $this->decoder->decode($stream); + $attestationObject = $parsed->getNormalizedData(); + Assertion::true($stream->isEOF(), 'Invalid attestation object. Presence of extra bytes.'); + $stream->close(); + Assertion::isArray($attestationObject, 'Invalid attestation object'); + Assertion::keyExists($attestationObject, 'authData', 'Invalid attestation object'); + Assertion::keyExists($attestationObject, 'fmt', 'Invalid attestation object'); + Assertion::keyExists($attestationObject, 'attStmt', 'Invalid attestation object'); + $authData = $attestationObject['authData']; + + $attestationStatementSupport = $this->attestationStatementSupportManager->get($attestationObject['fmt']); + $attestationStatement = $attestationStatementSupport->load($attestationObject); + + $authDataStream = new StringStream($authData); + $rp_id_hash = $authDataStream->read(32); + $flags = $authDataStream->read(1); + $signCount = $authDataStream->read(4); + $signCount = unpack('N', $signCount)[1]; + + $attestedCredentialData = null; + if (0 !== (\ord($flags) & self::FLAG_AT)) { + $aaguid = Uuid::fromBytes($authDataStream->read(16)); + $credentialLength = $authDataStream->read(2); + $credentialLength = unpack('n', $credentialLength)[1]; + $credentialId = $authDataStream->read($credentialLength); + $credentialPublicKey = $this->decoder->decode($authDataStream); + Assertion::isInstanceOf($credentialPublicKey, MapObject::class, 'The data does not contain a valid credential public key.'); + $attestedCredentialData = new AttestedCredentialData($aaguid, $credentialId, (string) $credentialPublicKey); + } + + $extension = null; + if (0 !== (\ord($flags) & self::FLAG_ED)) { + $extension = $this->decoder->decode($authDataStream); + $extension = AuthenticationExtensionsClientOutputsLoader::load($extension); + } + Assertion::true($authDataStream->isEOF(), 'Invalid authentication data. Presence of extra bytes.'); + $authDataStream->close(); + + $authenticatorData = new AuthenticatorData($authData, $rp_id_hash, $flags, $signCount, $attestedCredentialData, $extension); + + return new AttestationObject($data, $attestationStatement, $authenticatorData); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatement.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatement.php new file mode 100644 index 00000000..3d3e24a4 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatement.php @@ -0,0 +1,140 @@ +fmt = $fmt; + $this->attStmt = $attStmt; + $this->type = $type; + $this->trustPath = $trustPath; + } + + public static function createNone(string $fmt, array $attStmt, TrustPath $trustPath): self + { + return new self($fmt, $attStmt, self::TYPE_NONE, $trustPath); + } + + public static function createBasic(string $fmt, array $attStmt, TrustPath $trustPath): self + { + return new self($fmt, $attStmt, self::TYPE_BASIC, $trustPath); + } + + public static function createSelf(string $fmt, array $attStmt, TrustPath $trustPath): self + { + return new self($fmt, $attStmt, self::TYPE_SELF, $trustPath); + } + + public static function createAttCA(string $fmt, array $attStmt, TrustPath $trustPath): self + { + return new self($fmt, $attStmt, self::TYPE_ATTCA, $trustPath); + } + + public static function createEcdaa(string $fmt, array $attStmt, TrustPath $trustPath): self + { + return new self($fmt, $attStmt, self::TYPE_ECDAA, $trustPath); + } + + public function getFmt(): string + { + return $this->fmt; + } + + public function getAttStmt(): array + { + return $this->attStmt; + } + + public function has(string $key): bool + { + return \array_key_exists($key, $this->attStmt); + } + + /** + * @return mixed + */ + public function get(string $key) + { + Assertion::true($this->has($key), sprintf('The attestation statement has no key "%s".', $key)); + + return $this->attStmt[$key]; + } + + public function getTrustPath(): TrustPath + { + return $this->trustPath; + } + + public function getType(): string + { + return $this->type; + } + + public static function createFromArray(array $data): self + { + foreach (['fmt', 'attStmt', 'trustPath', 'type'] as $key) { + Assertion::keyExists($data, $key, sprintf('The key "%s" is missing', $key)); + } + + return new self( + $data['fmt'], + $data['attStmt'], + $data['type'], + TrustPathLoader::loadTrustPath($data['trustPath']) + ); + } + + public function jsonSerialize(): array + { + return [ + 'fmt' => $this->fmt, + 'attStmt' => $this->attStmt, + 'trustPath' => $this->trustPath, + 'type' => $this->type, + ]; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatementSupport.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatementSupport.php new file mode 100644 index 00000000..7826aab5 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatementSupport.php @@ -0,0 +1,25 @@ +attestationStatementSupports[$attestationStatementSupport->name()] = $attestationStatementSupport; + } + + public function has(string $name): bool + { + return \array_key_exists($name, $this->attestationStatementSupports); + } + + public function get(string $name): AttestationStatementSupport + { + Assertion::true($this->has($name), sprintf('The attestation statement format "%s" is not supported.', $name)); + + return $this->attestationStatementSupports[$name]; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/FidoU2FAttestationStatementSupport.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/FidoU2FAttestationStatementSupport.php new file mode 100644 index 00000000..cd27b330 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/FidoU2FAttestationStatementSupport.php @@ -0,0 +1,134 @@ +decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); + $this->metadataStatementRepository = $metadataStatementRepository; + } + + public function name(): string + { + return 'fido-u2f'; + } + + public function load(array $attestation): AttestationStatement + { + Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); + foreach (['sig', 'x5c'] as $key) { + Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); + } + $certificates = $attestation['attStmt']['x5c']; + Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with one certificate.'); + Assertion::count($certificates, 1, 'The attestation statement value "x5c" must be a list with one certificate.'); + Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with one certificate.'); + + reset($certificates); + $certificates = CertificateToolbox::convertAllDERToPEM($certificates); + $this->checkCertificate($certificates[0]); + + return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates)); + } + + public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool + { + Assertion::eq( + $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), + '00000000-0000-0000-0000-000000000000', + 'Invalid AAGUID for fido-u2f attestation statement. Shall be "00000000-0000-0000-0000-000000000000"' + ); + if (null !== $this->metadataStatementRepository) { + CertificateToolbox::checkAttestationMedata( + $attestationStatement, + $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), + [], + $this->metadataStatementRepository + ); + } + $trustPath = $attestationStatement->getTrustPath(); + Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); + $dataToVerify = "\0"; + $dataToVerify .= $authenticatorData->getRpIdHash(); + $dataToVerify .= $clientDataJSONHash; + $dataToVerify .= $authenticatorData->getAttestedCredentialData()->getCredentialId(); + $dataToVerify .= $this->extractPublicKey($authenticatorData->getAttestedCredentialData()->getCredentialPublicKey()); + + return 1 === openssl_verify($dataToVerify, $attestationStatement->get('sig'), $trustPath->getCertificates()[0], OPENSSL_ALGO_SHA256); + } + + private function extractPublicKey(?string $publicKey): string + { + Assertion::notNull($publicKey, 'The attested credential data does not contain a valid public key.'); + + $publicKeyStream = new StringStream($publicKey); + $coseKey = $this->decoder->decode($publicKeyStream); + Assertion::true($publicKeyStream->isEOF(), 'Invalid public key. Presence of extra bytes.'); + $publicKeyStream->close(); + Assertion::isInstanceOf($coseKey, MapObject::class, 'The attested credential data does not contain a valid public key.'); + + $coseKey = $coseKey->getNormalizedData(); + $ec2Key = new Ec2Key($coseKey + [Ec2Key::TYPE => 2, Ec2Key::DATA_CURVE => Ec2Key::CURVE_P256]); + + return "\x04".$ec2Key->x().$ec2Key->y(); + } + + private function checkCertificate(string $publicKey): void + { + try { + $resource = openssl_pkey_get_public($publicKey); + Assertion::isResource($resource, 'Unable to load the public key'); + } catch (Throwable $throwable) { + throw new InvalidArgumentException('Invalid certificate or certificate chain', 0, $throwable); + } + $details = openssl_pkey_get_details($resource); + Assertion::keyExists($details, 'ec', 'Invalid certificate or certificate chain'); + Assertion::keyExists($details['ec'], 'curve_name', 'Invalid certificate or certificate chain'); + Assertion::eq($details['ec']['curve_name'], 'prime256v1', 'Invalid certificate or certificate chain'); + Assertion::keyExists($details['ec'], 'curve_oid', 'Invalid certificate or certificate chain'); + Assertion::eq($details['ec']['curve_oid'], '1.2.840.10045.3.1.7', 'Invalid certificate or certificate chain'); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/NoneAttestationStatementSupport.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/NoneAttestationStatementSupport.php new file mode 100644 index 00000000..09287992 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/NoneAttestationStatementSupport.php @@ -0,0 +1,38 @@ +getAttStmt()); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/PackedAttestationStatementSupport.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/PackedAttestationStatementSupport.php new file mode 100644 index 00000000..4d4a01aa --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/PackedAttestationStatementSupport.php @@ -0,0 +1,204 @@ +decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); + $this->algorithmManager = $algorithmManager; + $this->metadataStatementRepository = $metadataStatementRepository; + } + + public function name(): string + { + return 'packed'; + } + + public function load(array $attestation): AttestationStatement + { + Assertion::keyExists($attestation['attStmt'], 'sig', 'The attestation statement value "sig" is missing.'); + Assertion::keyExists($attestation['attStmt'], 'alg', 'The attestation statement value "alg" is missing.'); + Assertion::string($attestation['attStmt']['sig'], 'The attestation statement value "sig" is missing.'); + switch (true) { + case \array_key_exists('x5c', $attestation['attStmt']): + return $this->loadBasicType($attestation); + case \array_key_exists('ecdaaKeyId', $attestation['attStmt']): + return $this->loadEcdaaType($attestation['attStmt']); + default: + return $this->loadEmptyType($attestation); + } + } + + public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool + { + $trustPath = $attestationStatement->getTrustPath(); + switch (true) { + case $trustPath instanceof CertificateTrustPath: + return $this->processWithCertificate($clientDataJSONHash, $attestationStatement, $authenticatorData, $trustPath); + case $trustPath instanceof EcdaaKeyIdTrustPath: + return $this->processWithECDAA(); + case $trustPath instanceof EmptyTrustPath: + return $this->processWithSelfAttestation($clientDataJSONHash, $attestationStatement, $authenticatorData); + default: + throw new InvalidArgumentException('Unsupported attestation statement'); + } + } + + private function loadBasicType(array $attestation): AttestationStatement + { + $certificates = $attestation['attStmt']['x5c']; + Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.'); + Assertion::minCount($certificates, 1, 'The attestation statement value "x5c" must be a list with at least one certificate.'); + $certificates = CertificateToolbox::convertAllDERToPEM($certificates); + + return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates)); + } + + private function loadEcdaaType(array $attestation): AttestationStatement + { + $ecdaaKeyId = $attestation['attStmt']['ecdaaKeyId']; + Assertion::string($ecdaaKeyId, 'The attestation statement value "ecdaaKeyId" is invalid.'); + + return AttestationStatement::createEcdaa($attestation['fmt'], $attestation['attStmt'], new EcdaaKeyIdTrustPath($attestation['ecdaaKeyId'])); + } + + private function loadEmptyType(array $attestation): AttestationStatement + { + return AttestationStatement::createSelf($attestation['fmt'], $attestation['attStmt'], new EmptyTrustPath()); + } + + private function checkCertificate(string $attestnCert, AuthenticatorData $authenticatorData): void + { + $parsed = openssl_x509_parse($attestnCert); + Assertion::isArray($parsed, 'Invalid certificate'); + + //Check version + Assertion::false(!isset($parsed['version']) || 2 !== $parsed['version'], 'Invalid certificate version'); + + //Check subject field + Assertion::false(!isset($parsed['name']) || false === mb_strpos($parsed['name'], '/OU=Authenticator Attestation'), 'Invalid certificate name. The Subject Organization Unit must be "Authenticator Attestation"'); + + //Check extensions + Assertion::false(!isset($parsed['extensions']) || !\is_array($parsed['extensions']), 'Certificate extensions are missing'); + + //Check certificate is not a CA cert + Assertion::false(!isset($parsed['extensions']['basicConstraints']) || 'CA:FALSE' !== $parsed['extensions']['basicConstraints'], 'The Basic Constraints extension must have the CA component set to false'); + + $attestedCredentialData = $authenticatorData->getAttestedCredentialData(); + Assertion::notNull($attestedCredentialData, 'No attested credential available'); + + // id-fido-gen-ce-aaguid OID check + Assertion::false(\in_array('1.3.6.1.4.1.45724.1.1.4', $parsed['extensions'], true) && !hash_equals($attestedCredentialData->getAaguid()->getBytes(), $parsed['extensions']['1.3.6.1.4.1.45724.1.1.4']), 'The value of the "aaguid" does not match with the certificate'); + } + + private function processWithCertificate(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData, CertificateTrustPath $trustPath): bool + { + $certificates = $trustPath->getCertificates(); + + if (null !== $this->metadataStatementRepository) { + $certificates = CertificateToolbox::checkAttestationMedata( + $attestationStatement, + $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), + $certificates, + $this->metadataStatementRepository + ); + } + + // Check leaf certificate + $this->checkCertificate($certificates[0], $authenticatorData); + + // Get the COSE algorithm identifier and the corresponding OpenSSL one + $coseAlgorithmIdentifier = (int) $attestationStatement->get('alg'); + $opensslAlgorithmIdentifier = Algorithms::getOpensslAlgorithmFor($coseAlgorithmIdentifier); + + // Verification of the signature + $signedData = $authenticatorData->getAuthData().$clientDataJSONHash; + $result = openssl_verify($signedData, $attestationStatement->get('sig'), $certificates[0], $opensslAlgorithmIdentifier); + + return 1 === $result; + } + + private function processWithECDAA(): bool + { + throw new RuntimeException('ECDAA not supported'); + } + + private function processWithSelfAttestation(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool + { + $attestedCredentialData = $authenticatorData->getAttestedCredentialData(); + Assertion::notNull($attestedCredentialData, 'No attested credential available'); + $credentialPublicKey = $attestedCredentialData->getCredentialPublicKey(); + Assertion::notNull($credentialPublicKey, 'No credential public key available'); + $publicKeyStream = new StringStream($credentialPublicKey); + $publicKey = $this->decoder->decode($publicKeyStream); + Assertion::true($publicKeyStream->isEOF(), 'Invalid public key. Presence of extra bytes.'); + $publicKeyStream->close(); + Assertion::isInstanceOf($publicKey, MapObject::class, 'The attested credential data does not contain a valid public key.'); + $publicKey = $publicKey->getNormalizedData(false); + $publicKey = new Key($publicKey); + Assertion::eq($publicKey->alg(), (int) $attestationStatement->get('alg'), 'The algorithm of the attestation statement and the key are not identical.'); + + $dataToVerify = $authenticatorData->getAuthData().$clientDataJSONHash; + $algorithm = $this->algorithmManager->get((int) $attestationStatement->get('alg')); + if (!$algorithm instanceof Signature) { + throw new RuntimeException('Invalid algorithm'); + } + $signature = CoseSignatureFixer::fix($attestationStatement->get('sig'), $algorithm); + + return $algorithm->verify($dataToVerify, $publicKey, $signature); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/TPMAttestationStatementSupport.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/TPMAttestationStatementSupport.php new file mode 100644 index 00000000..38605185 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestationStatement/TPMAttestationStatementSupport.php @@ -0,0 +1,325 @@ +metadataStatementRepository = $metadataStatementRepository; + } + + public function load(array $attestation): AttestationStatement + { + Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); + Assertion::keyNotExists($attestation['attStmt'], 'ecdaaKeyId', 'ECDAA not supported'); + foreach (['ver', 'ver', 'sig', 'alg', 'certInfo', 'pubArea'] as $key) { + Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); + } + Assertion::eq('2.0', $attestation['attStmt']['ver'], 'Invalid attestation object'); + + $certInfo = $this->checkCertInfo($attestation['attStmt']['certInfo']); + Assertion::eq('8017', bin2hex($certInfo['type']), 'Invalid attestation object'); + + $pubArea = $this->checkPubArea($attestation['attStmt']['pubArea']); + $pubAreaAttestedNameAlg = mb_substr($certInfo['attestedName'], 0, 2, '8bit'); + $pubAreaHash = hash($this->getTPMHash($pubAreaAttestedNameAlg), $attestation['attStmt']['pubArea'], true); + $attestedName = $pubAreaAttestedNameAlg.$pubAreaHash; + Assertion::eq($attestedName, $certInfo['attestedName'], 'Invalid attested name'); + + $attestation['attStmt']['parsedCertInfo'] = $certInfo; + $attestation['attStmt']['parsedPubArea'] = $pubArea; + + $certificates = CertificateToolbox::convertAllDERToPEM($attestation['attStmt']['x5c']); + Assertion::minCount($certificates, 1, 'The attestation statement value "x5c" must be a list with at least one certificate.'); + + return AttestationStatement::createAttCA( + $this->name(), + $attestation['attStmt'], + new CertificateTrustPath($certificates) + ); + } + + public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool + { + $attToBeSigned = $authenticatorData->getAuthData().$clientDataJSONHash; + $attToBeSignedHash = hash(Algorithms::getHashAlgorithmFor((int) $attestationStatement->get('alg')), $attToBeSigned, true); + Assertion::eq($attestationStatement->get('parsedCertInfo')['extraData'], $attToBeSignedHash, 'Invalid attestation hash'); + $this->checkUniquePublicKey( + $attestationStatement->get('parsedPubArea')['unique'], + $authenticatorData->getAttestedCredentialData()->getCredentialPublicKey() + ); + + switch (true) { + case $attestationStatement->getTrustPath() instanceof CertificateTrustPath: + return $this->processWithCertificate($clientDataJSONHash, $attestationStatement, $authenticatorData); + case $attestationStatement->getTrustPath() instanceof EcdaaKeyIdTrustPath: + return $this->processWithECDAA(); + default: + throw new InvalidArgumentException('Unsupported attestation statement'); + } + } + + private function checkUniquePublicKey(string $unique, string $cborPublicKey): void + { + $cborDecoder = new Decoder(new TagObjectManager(), new OtherObjectManager()); + $publicKey = $cborDecoder->decode(new StringStream($cborPublicKey)); + Assertion::isInstanceOf($publicKey, MapObject::class, 'Invalid public key'); + $key = new Key($publicKey->getNormalizedData(false)); + + switch ($key->type()) { + case Key::TYPE_OKP: + $uniqueFromKey = (new OkpKey($key->getData()))->x(); + break; + case Key::TYPE_EC2: + $ec2Key = new Ec2Key($key->getData()); + $uniqueFromKey = "\x04".$ec2Key->x().$ec2Key->y(); + break; + case Key::TYPE_RSA: + $uniqueFromKey = (new RsaKey($key->getData()))->n(); + break; + default: + throw new InvalidArgumentException('Invalid or unsupported key type.'); + } + + Assertion::eq($unique, $uniqueFromKey, 'Invalid pubArea.unique value'); + } + + private function checkCertInfo(string $data): array + { + $certInfo = new StringStream($data); + + $magic = $certInfo->read(4); + Assertion::eq('ff544347', bin2hex($magic), 'Invalid attestation object'); + + $type = $certInfo->read(2); + + $qualifiedSignerLength = unpack('n', $certInfo->read(2))[1]; + $qualifiedSigner = $certInfo->read($qualifiedSignerLength); //Ignored + + $extraDataLength = unpack('n', $certInfo->read(2))[1]; + $extraData = $certInfo->read($extraDataLength); + + $clockInfo = $certInfo->read(17); //Ignore + + $firmwareVersion = $certInfo->read(8); + + $attestedNameLength = unpack('n', $certInfo->read(2))[1]; + $attestedName = $certInfo->read($attestedNameLength); + + $attestedQualifiedNameLength = unpack('n', $certInfo->read(2))[1]; + $attestedQualifiedName = $certInfo->read($attestedQualifiedNameLength); //Ignore + Assertion::true($certInfo->isEOF(), 'Invalid certificate information. Presence of extra bytes.'); + $certInfo->close(); + + return [ + 'magic' => $magic, + 'type' => $type, + 'qualifiedSigner' => $qualifiedSigner, + 'extraData' => $extraData, + 'clockInfo' => $clockInfo, + 'firmwareVersion' => $firmwareVersion, + 'attestedName' => $attestedName, + 'attestedQualifiedName' => $attestedQualifiedName, + ]; + } + + private function checkPubArea(string $data): array + { + $pubArea = new StringStream($data); + + $type = $pubArea->read(2); + + $nameAlg = $pubArea->read(2); + + $objectAttributes = $pubArea->read(4); + + $authPolicyLength = unpack('n', $pubArea->read(2))[1]; + $authPolicy = $pubArea->read($authPolicyLength); + + $parameters = $this->getParameters($type, $pubArea); + + $uniqueLength = unpack('n', $pubArea->read(2))[1]; + $unique = $pubArea->read($uniqueLength); + Assertion::true($pubArea->isEOF(), 'Invalid public area. Presence of extra bytes.'); + $pubArea->close(); + + return [ + 'type' => $type, + 'nameAlg' => $nameAlg, + 'objectAttributes' => $objectAttributes, + 'authPolicy' => $authPolicy, + 'parameters' => $parameters, + 'unique' => $unique, + ]; + } + + private function getParameters(string $type, StringStream $stream): array + { + switch (bin2hex($type)) { + case '0001': + case '0014': + case '0016': + return [ + 'symmetric' => $stream->read(2), + 'scheme' => $stream->read(2), + 'keyBits' => unpack('n', $stream->read(2))[1], + 'exponent' => $this->getExponent($stream->read(4)), + ]; + case '0018': + return [ + 'symmetric' => $stream->read(2), + 'scheme' => $stream->read(2), + 'curveId' => $stream->read(2), + 'kdf' => $stream->read(2), + ]; + default: + throw new InvalidArgumentException('Unsupported type'); + } + } + + private function getExponent(string $exponent): string + { + return '00000000' === bin2hex($exponent) ? Base64Url::decode('AQAB') : $exponent; + } + + private function convertCertificatesToPem(array $certificates): array + { + foreach ($certificates as $k => $v) { + $tmp = '-----BEGIN CERTIFICATE-----'.PHP_EOL; + $tmp .= chunk_split(base64_encode($v), 64, PHP_EOL); + $tmp .= '-----END CERTIFICATE-----'.PHP_EOL; + $certificates[$k] = $tmp; + } + + return $certificates; + } + + private function getTPMHash(string $nameAlg): string + { + switch (bin2hex($nameAlg)) { + case '0004': + return 'sha1'; //: "TPM_ALG_SHA1", + case '000b': + return 'sha256'; //: "TPM_ALG_SHA256", + case '000c': + return 'sha384'; //: "TPM_ALG_SHA384", + case '000d': + return 'sha512'; //: "TPM_ALG_SHA512", + default: + throw new InvalidArgumentException('Unsupported hash algorithm'); + } + } + + private function processWithCertificate(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool + { + $trustPath = $attestationStatement->getTrustPath(); + Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); + + $certificates = $trustPath->getCertificates(); + if (null !== $this->metadataStatementRepository) { + $certificates = CertificateToolbox::checkAttestationMedata( + $attestationStatement, + $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), + $certificates, + $this->metadataStatementRepository + ); + } + + // Check certificate CA chain and returns the Attestation Certificate + $this->checkCertificate($certificates[0], $authenticatorData); + + // Get the COSE algorithm identifier and the corresponding OpenSSL one + $coseAlgorithmIdentifier = (int) $attestationStatement->get('alg'); + $opensslAlgorithmIdentifier = Algorithms::getOpensslAlgorithmFor($coseAlgorithmIdentifier); + + $result = openssl_verify($attestationStatement->get('certInfo'), $attestationStatement->get('sig'), $certificates[0], $opensslAlgorithmIdentifier); + + return 1 === $result; + } + + private function checkCertificate(string $attestnCert, AuthenticatorData $authenticatorData): void + { + $parsed = openssl_x509_parse($attestnCert); + Assertion::isArray($parsed, 'Invalid certificate'); + + //Check version + Assertion::false(!isset($parsed['version']) || 2 !== $parsed['version'], 'Invalid certificate version'); + + //Check subject field is empty + Assertion::false(!isset($parsed['subject']) || !\is_array($parsed['subject']) || 0 !== \count($parsed['subject']), 'Invalid certificate name. The Subject should be empty'); + + // Check period of validity + Assertion::keyExists($parsed, 'validFrom_time_t', 'Invalid certificate start date.'); + Assertion::integer($parsed['validFrom_time_t'], 'Invalid certificate start date.'); + $startDate = (new DateTimeImmutable())->setTimestamp($parsed['validFrom_time_t']); + Assertion::true($startDate < new DateTimeImmutable(), 'Invalid certificate start date.'); + + Assertion::keyExists($parsed, 'validTo_time_t', 'Invalid certificate end date.'); + Assertion::integer($parsed['validTo_time_t'], 'Invalid certificate end date.'); + $endDate = (new DateTimeImmutable())->setTimestamp($parsed['validTo_time_t']); + Assertion::true($endDate > new DateTimeImmutable(), 'Invalid certificate end date.'); + + //Check extensions + Assertion::false(!isset($parsed['extensions']) || !\is_array($parsed['extensions']), 'Certificate extensions are missing'); + + //Check subjectAltName + Assertion::false(!isset($parsed['extensions']['subjectAltName']), 'The "subjectAltName" is missing'); + + //Check extendedKeyUsage + Assertion::false(!isset($parsed['extensions']['extendedKeyUsage']), 'The "subjectAltName" is missing'); + Assertion::eq($parsed['extensions']['extendedKeyUsage'], '2.23.133.8.3', 'The "extendedKeyUsage" is invalid'); + + // id-fido-gen-ce-aaguid OID check + Assertion::false(\in_array('1.3.6.1.4.1.45724.1.1.4', $parsed['extensions'], true) && !hash_equals($authenticatorData->getAttestedCredentialData()->getAaguid()->getBytes(), $parsed['extensions']['1.3.6.1.4.1.45724.1.1.4']), 'The value of the "aaguid" does not match with the certificate'); + + // TODO: For attestationRoot in metadata.attestationRootCertificates, generate verification chain verifX5C by appending attestationRoot to the x5c. Try verifying verifX5C. If successful go to next step. If fail try next attestationRoot. If no attestationRoots left to try, fail. + } + + private function processWithECDAA(): bool + { + throw new RuntimeException('ECDAA not supported'); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestedCredentialData.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestedCredentialData.php new file mode 100644 index 00000000..0b6cb87f --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AttestedCredentialData.php @@ -0,0 +1,98 @@ +aaguid = $aaguid; + $this->credentialId = $credentialId; + $this->credentialPublicKey = $credentialPublicKey; + } + + public function getAaguid(): UuidInterface + { + return $this->aaguid; + } + + public function getCredentialId(): string + { + return $this->credentialId; + } + + public function getCredentialPublicKey(): ?string + { + return $this->credentialPublicKey; + } + + public static function createFromArray(array $json): self + { + Assertion::keyExists($json, 'aaguid', 'Invalid input. "aaguid" is missing.'); + Assertion::keyExists($json, 'credentialId', 'Invalid input. "credentialId" is missing.'); + switch (true) { + case 36 === mb_strlen($json['aaguid'], '8bit'): + $uuid = Uuid::fromString($json['aaguid']); + break; + default: // Kept for compatibility with old format + $decoded = base64_decode($json['aaguid'], true); + Assertion::string($decoded, 'Unable to decode the data'); + $uuid = Uuid::fromBytes($decoded); + } + $credentialId = base64_decode($json['credentialId'], true); + Assertion::string($credentialId, 'Unable to decode the data'); + + return new self( + $uuid, + $credentialId, + isset($json['credentialPublicKey']) ? base64_decode($json['credentialPublicKey'], true) : null + ); + } + + public function jsonSerialize(): array + { + $result = [ + 'aaguid' => $this->aaguid->toString(), + 'credentialId' => base64_encode($this->credentialId), + ]; + if (null !== $this->credentialPublicKey) { + $result['credentialPublicKey'] = base64_encode($this->credentialPublicKey); + } + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtension.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtension.php new file mode 100644 index 00000000..a7bb376a --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtension.php @@ -0,0 +1,59 @@ +name = $name; + $this->value = $value; + } + + public function name(): string + { + return $this->name; + } + + /** + * @return mixed + */ + public function value() + { + return $this->value; + } + + /** + * @return mixed + */ + public function jsonSerialize() + { + return $this->value; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientInputs.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientInputs.php new file mode 100644 index 00000000..863f0806 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientInputs.php @@ -0,0 +1,75 @@ +extensions[$extension->name()] = $extension; + } + + public static function createFromArray(array $json): self + { + $object = new self(); + foreach ($json as $k => $v) { + $object->add(new AuthenticationExtension($k, $v)); + } + + return $object; + } + + public function has(string $key): bool + { + return \array_key_exists($key, $this->extensions); + } + + /** + * @return mixed + */ + public function get(string $key) + { + Assertion::true($this->has($key), sprintf('The extension with key "%s" is not available', $key)); + + return $this->extensions[$key]; + } + + public function jsonSerialize(): array + { + return $this->extensions; + } + + public function getIterator(): Iterator + { + return new ArrayIterator($this->extensions); + } + + public function count(int $mode = COUNT_NORMAL): int + { + return \count($this->extensions, $mode); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputs.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputs.php new file mode 100644 index 00000000..0c986b6e --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputs.php @@ -0,0 +1,83 @@ +extensions[$extension->name()] = $extension; + } + + public static function createFromString(string $data): self + { + $data = json_decode($data, true); + Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); + Assertion::isArray($data, 'Invalid data'); + + return self::createFromArray($data); + } + + public static function createFromArray(array $json): self + { + $object = new self(); + foreach ($json as $k => $v) { + $object->add(new AuthenticationExtension($k, $v)); + } + + return $object; + } + + public function has(string $key): bool + { + return \array_key_exists($key, $this->extensions); + } + + /** + * @return mixed + */ + public function get(string $key) + { + Assertion::true($this->has($key), sprintf('The extension with key "%s" is not available', $key)); + + return $this->extensions[$key]; + } + + public function jsonSerialize(): array + { + return $this->extensions; + } + + public function getIterator(): Iterator + { + return new ArrayIterator($this->extensions); + } + + public function count(int $mode = COUNT_NORMAL): int + { + return \count($this->extensions, $mode); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputsLoader.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputsLoader.php new file mode 100644 index 00000000..51cc4bb1 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputsLoader.php @@ -0,0 +1,34 @@ +getNormalizedData(); + $extensions = new AuthenticationExtensionsClientOutputs(); + foreach ($data as $key => $value) { + Assertion::string($key, 'Invalid extension key'); + $extensions->add(new AuthenticationExtension($key, $value)); + } + + return $extensions; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputChecker.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputChecker.php new file mode 100644 index 00000000..e9038076 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputChecker.php @@ -0,0 +1,22 @@ +checkers[] = $checker; + } + + /** + * @throws ExtensionOutputError + */ + public function check(AuthenticationExtensionsClientOutputs $extensions): void + { + foreach ($this->checkers as $checker) { + $checker->check($extensions); + } + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputError.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputError.php new file mode 100644 index 00000000..2b552b18 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputError.php @@ -0,0 +1,36 @@ +authenticationExtension = $authenticationExtension; + } + + public function getAuthenticationExtension(): AuthenticationExtension + { + return $this->authenticationExtension; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAssertionResponse.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAssertionResponse.php new file mode 100644 index 00000000..19132c3b --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAssertionResponse.php @@ -0,0 +1,67 @@ +authenticatorData = $authenticatorData; + $this->signature = $signature; + $this->userHandle = $userHandle; + } + + public function getAuthenticatorData(): AuthenticatorData + { + return $this->authenticatorData; + } + + public function getSignature(): string + { + return $this->signature; + } + + public function getUserHandle(): ?string + { + if (null === $this->userHandle || '' === $this->userHandle) { + return $this->userHandle; + } + + $decoded = base64_decode($this->userHandle, true); + Assertion::string($decoded, 'Unable to decode the data'); + + return $decoded; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAssertionResponseValidator.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAssertionResponseValidator.php new file mode 100644 index 00000000..8998493a --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAssertionResponseValidator.php @@ -0,0 +1,205 @@ +publicKeyCredentialSourceRepository = $publicKeyCredentialSourceRepository; + $this->decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); + $this->tokenBindingHandler = $tokenBindingHandler; + $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler; + $this->algorithmManager = $algorithmManager; + } + + /** + * @see https://www.w3.org/TR/webauthn/#verifying-assertion + */ + public function check(string $credentialId, AuthenticatorAssertionResponse $authenticatorAssertionResponse, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ServerRequestInterface $request, ?string $userHandle): PublicKeyCredentialSource + { + /* @see 7.2.1 */ + if (0 !== \count($publicKeyCredentialRequestOptions->getAllowCredentials())) { + Assertion::true($this->isCredentialIdAllowed($credentialId, $publicKeyCredentialRequestOptions->getAllowCredentials()), 'The credential ID is not allowed.'); + } + + /* @see 7.2.2 */ + $publicKeyCredentialSource = $this->publicKeyCredentialSourceRepository->findOneByCredentialId($credentialId); + Assertion::notNull($publicKeyCredentialSource, 'The credential ID is invalid.'); + + /* @see 7.2.3 */ + $attestedCredentialData = $publicKeyCredentialSource->getAttestedCredentialData(); + $credentialUserHandle = $publicKeyCredentialSource->getUserHandle(); + $responseUserHandle = $authenticatorAssertionResponse->getUserHandle(); + + /* @see 7.2.2 User Handle*/ + if (null !== $userHandle) { //If the user was identified before the authentication ceremony was initiated, + Assertion::eq($credentialUserHandle, $userHandle, 'Invalid user handle'); + if (null !== $responseUserHandle && '' !== $responseUserHandle) { + Assertion::eq($credentialUserHandle, $responseUserHandle, 'Invalid user handle'); + } + } else { + Assertion::notEmpty($responseUserHandle, 'User handle is mandatory'); + Assertion::eq($credentialUserHandle, $responseUserHandle, 'Invalid user handle'); + } + + $credentialPublicKey = $attestedCredentialData->getCredentialPublicKey(); + Assertion::notNull($credentialPublicKey, 'No public key available.'); + $stream = new StringStream($credentialPublicKey); + $credentialPublicKeyStream = $this->decoder->decode($stream); + Assertion::true($stream->isEOF(), 'Invalid key. Presence of extra bytes.'); + $stream->close(); + + /** @see 7.2.4 */ + /** @see 7.2.5 */ + //Nothing to do. Use of objects directly + + /** @see 7.2.6 */ + $C = $authenticatorAssertionResponse->getClientDataJSON(); + + /* @see 7.2.7 */ + Assertion::eq('webauthn.get', $C->getType(), 'The client data type is not "webauthn.get".'); + + /* @see 7.2.8 */ + Assertion::true(hash_equals($publicKeyCredentialRequestOptions->getChallenge(), $C->getChallenge()), 'Invalid challenge.'); + + /** @see 7.2.9 */ + $rpId = $publicKeyCredentialRequestOptions->getRpId() ?? $request->getUri()->getHost(); + $rpIdLength = mb_strlen($rpId); + $parsedRelyingPartyId = parse_url($C->getOrigin()); + Assertion::isArray($parsedRelyingPartyId, 'Invalid origin'); + $scheme = $parsedRelyingPartyId['scheme'] ?? ''; + Assertion::eq('https', $scheme, 'Invalid scheme. HTTPS required.'); + $clientDataRpId = $parsedRelyingPartyId['host'] ?? ''; + Assertion::notEmpty($clientDataRpId, 'Invalid origin rpId.'); + Assertion::eq(mb_substr($clientDataRpId, -$rpIdLength), $rpId, 'rpId mismatch.'); + + /* @see 7.2.10 */ + if (null !== $C->getTokenBinding()) { + $this->tokenBindingHandler->check($C->getTokenBinding(), $request); + } + + /** @see 7.2.11 */ + $facetId = $this->getFacetId($rpId, $publicKeyCredentialRequestOptions->getExtensions(), $authenticatorAssertionResponse->getAuthenticatorData()->getExtensions()); + $rpIdHash = hash('sha256', $rpId, true); + Assertion::true(hash_equals($rpIdHash, $authenticatorAssertionResponse->getAuthenticatorData()->getRpIdHash()), 'rpId hash mismatch.'); + + /* @see 7.2.12 */ + /* @see 7.2.13 */ + if (AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED === $publicKeyCredentialRequestOptions->getUserVerification()) { + Assertion::true($authenticatorAssertionResponse->getAuthenticatorData()->isUserPresent(), 'User was not present'); + Assertion::true($authenticatorAssertionResponse->getAuthenticatorData()->isUserVerified(), 'User authentication required.'); + } + + /* @see 7.2.14 */ + $extensions = $authenticatorAssertionResponse->getAuthenticatorData()->getExtensions(); + if (null !== $extensions) { + $this->extensionOutputCheckerHandler->check($extensions); + } + + /** @see 7.2.15 */ + $getClientDataJSONHash = hash('sha256', $authenticatorAssertionResponse->getClientDataJSON()->getRawData(), true); + + /* @see 7.2.16 */ + $dataToVerify = $authenticatorAssertionResponse->getAuthenticatorData()->getAuthData().$getClientDataJSONHash; + $signature = $authenticatorAssertionResponse->getSignature(); + $coseKey = new Key($credentialPublicKeyStream->getNormalizedData()); + $algorithm = $this->algorithmManager->get($coseKey->alg()); + Assertion::isInstanceOf($algorithm, Signature::class, 'Invalid algorithm identifier. Should refer to a signature algorithm'); + $signature = CoseSignatureFixer::fix($signature, $algorithm); + Assertion::true($algorithm->verify($dataToVerify, $coseKey, $signature), 'Invalid signature.'); + + /* @see 7.2.17 */ + $storedCounter = $publicKeyCredentialSource->getCounter(); + $currentCounter = $authenticatorAssertionResponse->getAuthenticatorData()->getSignCount(); + if (0 !== $currentCounter || 0 !== $storedCounter) { + Assertion::greaterThan($currentCounter, $storedCounter, 'Invalid counter.'); + } + $publicKeyCredentialSource->setCounter($currentCounter); + $this->publicKeyCredentialSourceRepository->saveCredentialSource($publicKeyCredentialSource); + + /* @see 7.2.18 */ + //All good. We can continue. + return $publicKeyCredentialSource; + } + + private function isCredentialIdAllowed(string $credentialId, array $allowedCredentials): bool + { + foreach ($allowedCredentials as $allowedCredential) { + if (hash_equals($allowedCredential->getId(), $credentialId)) { + return true; + } + } + + return false; + } + + private function getFacetId(string $rpId, AuthenticationExtensionsClientInputs $authenticationExtensionsClientInputs, ?AuthenticationExtensionsClientOutputs $authenticationExtensionsClientOutputs): string + { + switch (true) { + case !$authenticationExtensionsClientInputs->has('appid'): + return $rpId; + case null === $authenticationExtensionsClientOutputs: + return $rpId; + case !$authenticationExtensionsClientOutputs->has('appid'): + return $rpId; + case true !== $authenticationExtensionsClientOutputs->get('appid'): + return $rpId; + default: + return $authenticationExtensionsClientInputs->get('appid'); + } + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAttestationResponse.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAttestationResponse.php new file mode 100644 index 00000000..cf9c3a40 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAttestationResponse.php @@ -0,0 +1,38 @@ +attestationObject = $attestationObject; + } + + public function getAttestationObject(): AttestationObject + { + return $this->attestationObject; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAttestationResponseValidator.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAttestationResponseValidator.php new file mode 100644 index 00000000..072d621b --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorAttestationResponseValidator.php @@ -0,0 +1,152 @@ +attestationStatementSupportManager = $attestationStatementSupportManager; + $this->publicKeyCredentialSource = $publicKeyCredentialSource; + $this->tokenBindingHandler = $tokenBindingHandler; + $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler; + } + + /** + * @see https://www.w3.org/TR/webauthn/#registering-a-new-credential + */ + public function check(AuthenticatorAttestationResponse $authenticatorAttestationResponse, PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, ServerRequestInterface $request): PublicKeyCredentialSource + { + /** @see 7.1.1 */ + //Nothing to do + + /** @see 7.1.2 */ + $C = $authenticatorAttestationResponse->getClientDataJSON(); + + /* @see 7.1.3 */ + Assertion::eq('webauthn.create', $C->getType(), 'The client data type is not "webauthn.create".'); + + /* @see 7.1.4 */ + Assertion::true(hash_equals($publicKeyCredentialCreationOptions->getChallenge(), $C->getChallenge()), 'Invalid challenge.'); + + /** @see 7.1.5 */ + $rpId = $publicKeyCredentialCreationOptions->getRp()->getId() ?? $request->getUri()->getHost(); + + $parsedRelyingPartyId = parse_url($C->getOrigin()); + Assertion::isArray($parsedRelyingPartyId, sprintf('The origin URI "%s" is not valid', $C->getOrigin())); + Assertion::keyExists($parsedRelyingPartyId, 'scheme', 'Invalid origin rpId.'); + $scheme = $parsedRelyingPartyId['scheme'] ?? ''; + Assertion::eq('https', $scheme, 'Invalid scheme. HTTPS required.'); + $clientDataRpId = $parsedRelyingPartyId['host'] ?? ''; + Assertion::notEmpty($clientDataRpId, 'Invalid origin rpId.'); + $rpIdLength = mb_strlen($rpId); + Assertion::eq(mb_substr($clientDataRpId, -$rpIdLength), $rpId, 'rpId mismatch.'); + + /* @see 7.1.6 */ + if (null !== $C->getTokenBinding()) { + $this->tokenBindingHandler->check($C->getTokenBinding(), $request); + } + + /** @see 7.1.7 */ + $clientDataJSONHash = hash('sha256', $authenticatorAttestationResponse->getClientDataJSON()->getRawData(), true); + + /** @see 7.1.8 */ + $attestationObject = $authenticatorAttestationResponse->getAttestationObject(); + + /** @see 7.1.9 */ + $rpIdHash = hash('sha256', $rpId, true); + Assertion::true(hash_equals($rpIdHash, $attestationObject->getAuthData()->getRpIdHash()), 'rpId hash mismatch.'); + + /* @see 7.1.10 */ + /* @see 7.1.11 */ + if (AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED === $publicKeyCredentialCreationOptions->getAuthenticatorSelection()->getUserVerification()) { + Assertion::true($attestationObject->getAuthData()->isUserPresent(), 'User was not present'); + Assertion::true($attestationObject->getAuthData()->isUserVerified(), 'User authentication required.'); + } + + /* @see 7.1.12 */ + $extensions = $attestationObject->getAuthData()->getExtensions(); + if (null !== $extensions) { + $this->extensionOutputCheckerHandler->check($extensions); + } + + /** @see 7.1.13 */ + $fmt = $attestationObject->getAttStmt()->getFmt(); + Assertion::true($this->attestationStatementSupportManager->has($fmt), 'Unsupported attestation statement format.'); + + /** @see 7.1.14 */ + $attestationStatementSupport = $this->attestationStatementSupportManager->get($fmt); + Assertion::true($attestationStatementSupport->isValid($clientDataJSONHash, $attestationObject->getAttStmt(), $attestationObject->getAuthData()), 'Invalid attestation statement.'); + + /* @see 7.1.15 */ + /* @see 7.1.16 */ + /* @see 7.1.17 */ + Assertion::true($attestationObject->getAuthData()->hasAttestedCredentialData(), 'There is no attested credential data.'); + $attestedCredentialData = $attestationObject->getAuthData()->getAttestedCredentialData(); + Assertion::notNull($attestedCredentialData, 'There is no attested credential data.'); + $credentialId = $attestedCredentialData->getCredentialId(); + Assertion::null($this->publicKeyCredentialSource->findOneByCredentialId($credentialId), 'The credential ID already exists.'); + + /* @see 7.1.18 */ + /* @see 7.1.19 */ + return $this->createPublicKeyCredentialSource( + $credentialId, + $attestedCredentialData, + $attestationObject, + $publicKeyCredentialCreationOptions->getUser()->getId() + ); + } + + private function createPublicKeyCredentialSource(string $credentialId, AttestedCredentialData $attestedCredentialData, AttestationObject $attestationObject, string $userHandle): PublicKeyCredentialSource + { + return new PublicKeyCredentialSource( + $credentialId, + PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, + [], + $attestationObject->getAttStmt()->getType(), + $attestationObject->getAttStmt()->getTrustPath(), + $attestedCredentialData->getAaguid(), + $attestedCredentialData->getCredentialPublicKey(), + $userHandle, + $attestationObject->getAuthData()->getSignCount() + ); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorData.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorData.php new file mode 100644 index 00000000..d7141b63 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorData.php @@ -0,0 +1,124 @@ +rpIdHash = $rpIdHash; + $this->flags = $flags; + $this->signCount = $signCount; + $this->attestedCredentialData = $attestedCredentialData; + $this->extensions = $extensions; + $this->authData = $authData; + } + + public function getAuthData(): string + { + return $this->authData; + } + + public function getRpIdHash(): string + { + return $this->rpIdHash; + } + + public function isUserPresent(): bool + { + return 0 !== (\ord($this->flags) & self::FLAG_UP) ? true : false; + } + + public function isUserVerified(): bool + { + return 0 !== (\ord($this->flags) & self::FLAG_UV) ? true : false; + } + + public function hasAttestedCredentialData(): bool + { + return 0 !== (\ord($this->flags) & self::FLAG_AT) ? true : false; + } + + public function hasExtensions(): bool + { + return 0 !== (\ord($this->flags) & self::FLAG_ED) ? true : false; + } + + public function getReservedForFutureUse1(): int + { + return \ord($this->flags) & self::FLAG_RFU1; + } + + public function getReservedForFutureUse2(): int + { + return \ord($this->flags) & self::FLAG_RFU2; + } + + public function getSignCount(): int + { + return $this->signCount; + } + + public function getAttestedCredentialData(): ?AttestedCredentialData + { + return $this->attestedCredentialData; + } + + public function getExtensions(): ?AuthenticationExtensionsClientOutputs + { + return null !== $this->extensions && $this->hasExtensions() ? $this->extensions : null; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorResponse.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorResponse.php new file mode 100644 index 00000000..05f3535f --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorResponse.php @@ -0,0 +1,35 @@ +clientDataJSON = $clientDataJSON; + } + + public function getClientDataJSON(): CollectedClientData + { + return $this->clientDataJSON; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorSelectionCriteria.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorSelectionCriteria.php new file mode 100644 index 00000000..6fb49ac1 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/AuthenticatorSelectionCriteria.php @@ -0,0 +1,96 @@ +authenticatorAttachment = $authenticatorAttachment; + $this->requireResidentKey = $requireResidentKey; + $this->userVerification = $userVerification; + } + + public function getAuthenticatorAttachment(): ?string + { + return $this->authenticatorAttachment; + } + + public function isRequireResidentKey(): bool + { + return $this->requireResidentKey; + } + + public function getUserVerification(): string + { + return $this->userVerification; + } + + public static function createFromString(string $data): self + { + $data = json_decode($data, true); + Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); + Assertion::isArray($data, 'Invalid data'); + + return self::createFromArray($data); + } + + public static function createFromArray(array $json): self + { + return new self( + $json['authenticatorAttachment'] ?? null, + $json['requireResidentKey'] ?? false, + $json['userVerification'] ?? self::USER_VERIFICATION_REQUIREMENT_PREFERRED + ); + } + + public function jsonSerialize(): array + { + $json = [ + 'requireResidentKey' => $this->requireResidentKey, + 'userVerification' => $this->userVerification, + ]; + if (null !== $this->authenticatorAttachment) { + $json['authenticatorAttachment'] = $this->authenticatorAttachment; + } + + return $json; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/CertificateToolbox.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/CertificateToolbox.php new file mode 100644 index 00000000..5b47144b --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/CertificateToolbox.php @@ -0,0 +1,209 @@ +start(); + while ($process->isRunning()) { + } + foreach ($filenames as $filename) { + $result = unlink($filename); + Assertion::true($result, 'Unable to delete temporary file'); + } + + if (!$process->isSuccessful()) { + throw new InvalidArgumentException('Invalid certificate or certificate chain. Error is: '.$process->getErrorOutput()); + } + } + + public static function checkAttestationMedata(AttestationStatement $attestationStatement, string $aaguid, array $certificates, MetadataStatementRepository $metadataStatementRepository): array + { + $metadataStatement = $metadataStatementRepository->findOneByAAGUID($aaguid); + if (null === $metadataStatement) { + //Check certificate CA chain + self::checkChain($certificates); + + return $certificates; + } + + //FIXME: to decide later if relevant + /*Assertion::eq('fido2', $metadataStatement->getProtocolFamily(), sprintf('The protocol family of the authenticator "%s" should be "fido2". Got "%s".', $aaguid, $metadataStatement->getProtocolFamily())); + if (null !== $metadataStatement->getAssertionScheme()) { + Assertion::eq('FIDOV2', $metadataStatement->getAssertionScheme(), sprintf('The assertion scheme of the authenticator "%s" should be "FIDOV2". Got "%s".', $aaguid, $metadataStatement->getAssertionScheme())); + }*/ + + // Check Attestation Type is allowed + if (0 !== \count($metadataStatement->getAttestationTypes())) { + $type = self::getAttestationType($attestationStatement); + Assertion::inArray($type, $metadataStatement->getAttestationTypes(), 'Invalid attestation statement. The attestation type is not allowed for this authenticator'); + } + + $attestationRootCertificates = $metadataStatement->getAttestationRootCertificates(); + if (0 === \count($attestationRootCertificates)) { + self::checkChain($certificates); + + return $certificates; + } + + foreach ($attestationRootCertificates as $key => $attestationRootCertificate) { + $attestationRootCertificates[$key] = self::fixPEMStructure($attestationRootCertificate); + } + + //Check certificate CA chain + self::checkChain($certificates, $attestationRootCertificates); + + return $certificates; + } + + private static function getAttestationType(AttestationStatement $attestationStatement): int + { + switch ($attestationStatement->getType()) { + case AttestationStatement::TYPE_BASIC: + return MetadataStatement::ATTESTATION_BASIC_FULL; + case AttestationStatement::TYPE_SELF: + return MetadataStatement::ATTESTATION_BASIC_SURROGATE; + case AttestationStatement::TYPE_ATTCA: + return MetadataStatement::ATTESTATION_ATTCA; + case AttestationStatement::TYPE_ECDAA: + return MetadataStatement::ATTESTATION_ECDAA; + default: + throw new InvalidArgumentException('Invalid attestation type'); + } + } + + public static function fixPEMStructure(string $certificate): string + { + $pemCert = '-----BEGIN CERTIFICATE-----'.PHP_EOL; + $pemCert .= chunk_split($certificate, 64, PHP_EOL); + $pemCert .= '-----END CERTIFICATE-----'.PHP_EOL; + + return $pemCert; + } + + public static function convertDERToPEM(string $certificate): string + { + $derCertificate = self::unusedBytesFix($certificate); + + return self::fixPEMStructure(base64_encode($derCertificate)); + } + + public static function convertAllDERToPEM(array $certificates): array + { + $certs = []; + foreach ($certificates as $publicKey) { + $certs[] = self::convertDERToPEM($publicKey); + } + + return $certs; + } + + private static function unusedBytesFix(string $certificate): string + { + $certificateHash = hash('sha256', $certificate); + if (\in_array($certificateHash, self::getCertificateHashes(), true)) { + $certificate[mb_strlen($certificate, '8bit') - 257] = "\0"; + } + + return $certificate; + } + + /** + * @param string[] $certificates + */ + private static function checkCertificatesValidity(array $certificates): void + { + foreach ($certificates as $certificate) { + $parsed = openssl_x509_parse($certificate); + Assertion::isArray($parsed, 'Unable to read the certificate'); + Assertion::keyExists($parsed, 'validTo_time_t', 'The certificate has no validity period'); + Assertion::keyExists($parsed, 'validFrom_time_t', 'The certificate has no validity period'); + Assertion::lessOrEqualThan(time(), $parsed['validTo_time_t'], 'The certificate expired'); + Assertion::greaterOrEqualThan(time(), $parsed['validFrom_time_t'], 'The certificate is not usable yet'); + } + } + + /** + * @return string[] + */ + private static function getCertificateHashes(): array + { + return [ + '349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8', + 'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f', + '1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae', + 'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb', + '6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897', + 'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511', + ]; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/CollectedClientData.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/CollectedClientData.php new file mode 100644 index 00000000..8b33f42a --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/CollectedClientData.php @@ -0,0 +1,138 @@ +type = $this->findData($data, 'type'); + $this->challenge = $this->findData($data, 'challenge', true, true); + $this->origin = $this->findData($data, 'origin'); + $this->tokenBinding = $this->findData($data, 'tokenBinding', false); + $this->rawData = $rawData; + $this->data = $data; + } + + public static function createFormJson(string $data): self + { + $rawData = Base64Url::decode($data); + $json = json_decode($rawData, true); + Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid collected client data'); + Assertion::isArray($json, 'Invalid collected client data'); + + return new self($rawData, $json); + } + + public function getType(): string + { + return $this->type; + } + + public function getChallenge(): string + { + return $this->challenge; + } + + public function getOrigin(): string + { + return $this->origin; + } + + public function getTokenBinding(): ?TokenBinding + { + return null === $this->tokenBinding ? null : TokenBinding::createFormArray($this->tokenBinding); + } + + public function getRawData(): string + { + return $this->rawData; + } + + /** + * @return string[] + */ + public function all(): array + { + return array_keys($this->data); + } + + public function has(string $key): bool + { + return \array_key_exists($key, $this->data); + } + + /** + * @return mixed + */ + public function get(string $key) + { + if (!$this->has($key)) { + throw new InvalidArgumentException(sprintf('The key "%s" is missing', $key)); + } + + return $this->data[$key]; + } + + /** + * @return mixed|null + */ + private function findData(array $json, string $key, bool $isRequired = true, bool $isB64 = false) + { + if (!\array_key_exists($key, $json)) { + if ($isRequired) { + throw new InvalidArgumentException(sprintf('The key "%s" is missing', $key)); + } + + return; + } + + return $isB64 ? Base64Url::decode($json[$key]) : $json[$key]; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/Credential.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/Credential.php new file mode 100644 index 00000000..3d981c8a --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/Credential.php @@ -0,0 +1,46 @@ +id = $id; + $this->type = $type; + } + + public function getId(): string + { + return $this->id; + } + + public function getType(): string + { + return $this->type; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredential.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredential.php new file mode 100644 index 00000000..956309d5 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredential.php @@ -0,0 +1,65 @@ +rawId = $rawId; + $this->response = $response; + } + + public function getRawId(): string + { + return $this->rawId; + } + + public function getResponse(): AuthenticatorResponse + { + return $this->response; + } + + /** + * @param string[] $transport + */ + public function getPublicKeyCredentialDescriptor(array $transport = []): PublicKeyCredentialDescriptor + { + return new PublicKeyCredentialDescriptor($this->getType(), $this->getRawId(), $transport); + } + + public function __toString() + { + $encoded = json_encode($this); + Assertion::string($encoded, 'Unable to encode the data'); + + return $encoded; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialCreationOptions.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialCreationOptions.php new file mode 100644 index 00000000..26828814 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialCreationOptions.php @@ -0,0 +1,177 @@ +rp = $rp; + $this->user = $user; + $this->pubKeyCredParams = array_values($pubKeyCredParams); + $this->excludeCredentials = array_values($excludeCredentials); + $this->authenticatorSelection = $authenticatorSelection; + $this->attestation = $attestation; + } + + public function getRp(): PublicKeyCredentialRpEntity + { + return $this->rp; + } + + public function getUser(): PublicKeyCredentialUserEntity + { + return $this->user; + } + + /** + * @return PublicKeyCredentialParameters[] + */ + public function getPubKeyCredParams(): array + { + return $this->pubKeyCredParams; + } + + /** + * @return PublicKeyCredentialDescriptor[] + */ + public function getExcludeCredentials(): array + { + return $this->excludeCredentials; + } + + public function getAuthenticatorSelection(): AuthenticatorSelectionCriteria + { + return $this->authenticatorSelection; + } + + public function getAttestation(): string + { + return $this->attestation; + } + + public static function createFromString(string $data): PublicKeyCredentialOptions + { + $data = json_decode($data, true); + Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); + Assertion::isArray($data, 'Invalid data'); + + return self::createFromArray($data); + } + + public static function createFromArray(array $json): PublicKeyCredentialOptions + { + Assertion::keyExists($json, 'rp', 'Invalid input. "rp" is missing.'); + Assertion::keyExists($json, 'pubKeyCredParams', 'Invalid input. "pubKeyCredParams" is missing.'); + Assertion::isArray($json['pubKeyCredParams'], 'Invalid input. "pubKeyCredParams" is not an array.'); + Assertion::keyExists($json, 'challenge', 'Invalid input. "challenge" is missing.'); + Assertion::keyExists($json, 'attestation', 'Invalid input. "attestation" is missing.'); + Assertion::keyExists($json, 'user', 'Invalid input. "user" is missing.'); + Assertion::keyExists($json, 'authenticatorSelection', 'Invalid input. "authenticatorSelection" is missing.'); + + $pubKeyCredParams = []; + foreach ($json['pubKeyCredParams'] as $pubKeyCredParam) { + $pubKeyCredParams[] = PublicKeyCredentialParameters::createFromArray($pubKeyCredParam); + } + $excludeCredentials = []; + if (isset($json['excludeCredentials'])) { + foreach ($json['excludeCredentials'] as $excludeCredential) { + $excludeCredentials[] = PublicKeyCredentialDescriptor::createFromArray($excludeCredential); + } + } + + return new self( + PublicKeyCredentialRpEntity::createFromArray($json['rp']), + PublicKeyCredentialUserEntity::createFromArray($json['user']), + Base64Url::decode($json['challenge']), + $pubKeyCredParams, + $json['timeout'] ?? null, + $excludeCredentials, + AuthenticatorSelectionCriteria::createFromArray($json['authenticatorSelection']), + $json['attestation'], + isset($json['extensions']) ? AuthenticationExtensionsClientInputs::createFromArray($json['extensions']) : new AuthenticationExtensionsClientInputs() + ); + } + + public function jsonSerialize(): array + { + $json = [ + 'rp' => $this->rp, + 'pubKeyCredParams' => $this->pubKeyCredParams, + 'challenge' => Base64Url::encode($this->challenge), + 'attestation' => $this->attestation, + 'user' => $this->user, + 'authenticatorSelection' => $this->authenticatorSelection, + ]; + + if (0 !== \count($this->excludeCredentials)) { + $json['excludeCredentials'] = $this->excludeCredentials; + } + + if (0 !== $this->extensions->count()) { + $json['extensions'] = $this->extensions; + } + + if (null !== $this->timeout) { + $json['timeout'] = $this->timeout; + } + + return $json; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptor.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptor.php new file mode 100644 index 00000000..683f81cc --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptor.php @@ -0,0 +1,105 @@ +type = $type; + $this->id = $id; + $this->transports = $transports; + } + + public function getType(): string + { + return $this->type; + } + + public function getId(): string + { + return $this->id; + } + + /** + * @return string[] + */ + public function getTransports(): array + { + return $this->transports; + } + + public static function createFromString(string $data): self + { + $data = json_decode($data, true); + Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); + Assertion::isArray($data, 'Invalid data'); + + return self::createFromArray($data); + } + + public static function createFromArray(array $json): self + { + Assertion::keyExists($json, 'type', 'Invalid input. "type" is missing.'); + Assertion::keyExists($json, 'id', 'Invalid input. "id" is missing.'); + + return new self( + $json['type'], + Base64Url::decode($json['id']), + $json['transports'] ?? [] + ); + } + + public function jsonSerialize(): array + { + $json = [ + 'type' => $this->type, + 'id' => Base64Url::encode($this->id), + ]; + if (0 !== \count($this->transports)) { + $json['transports'] = $this->transports; + } + + return $json; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptorCollection.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptorCollection.php new file mode 100644 index 00000000..8364c6b4 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptorCollection.php @@ -0,0 +1,82 @@ +publicKeyCredentialDescriptors[$publicKeyCredentialDescriptor->getId()] = $publicKeyCredentialDescriptor; + } + + public function has(string $id): bool + { + return \array_key_exists($id, $this->publicKeyCredentialDescriptors); + } + + public function remove(string $id): void + { + if (!$this->has($id)) { + return; + } + + unset($this->publicKeyCredentialDescriptors[$id]); + } + + public function getIterator(): Iterator + { + return new ArrayIterator($this->publicKeyCredentialDescriptors); + } + + public function count(int $mode = COUNT_NORMAL): int + { + return \count($this->publicKeyCredentialDescriptors, $mode); + } + + public function jsonSerialize(): array + { + return array_values($this->publicKeyCredentialDescriptors); + } + + public static function createFromString(string $data): self + { + $data = json_decode($data, true); + Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); + Assertion::isArray($data, 'Invalid data'); + + return self::createFromArray($data); + } + + public static function createFromArray(array $json): self + { + $collection = new self(); + foreach ($json as $item) { + $collection->add(PublicKeyCredentialDescriptor::createFromArray($item)); + } + + return $collection; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialEntity.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialEntity.php new file mode 100644 index 00000000..959bb17a --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialEntity.php @@ -0,0 +1,57 @@ +name = $name; + $this->icon = $icon; + } + + public function getName(): string + { + return $this->name; + } + + public function getIcon(): ?string + { + return $this->icon; + } + + public function jsonSerialize(): array + { + $json = [ + 'name' => $this->name, + ]; + if (null !== $this->icon) { + $json['icon'] = $this->icon; + } + + return $json; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialLoader.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialLoader.php new file mode 100644 index 00000000..9ff31fd8 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialLoader.php @@ -0,0 +1,130 @@ +decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); + $this->attestationObjectLoader = $attestationObjectLoader; + } + + public function loadArray(array $json): PublicKeyCredential + { + foreach (['id', 'rawId', 'type'] as $key) { + Assertion::keyExists($json, $key, sprintf('The parameter "%s" is missing', $key)); + Assertion::string($json[$key], sprintf('The parameter "%s" shall be a string', $key)); + } + Assertion::keyExists($json, 'response', 'The parameter "response" is missing'); + Assertion::isArray($json['response'], 'The parameter "response" shall be an array'); + Assertion::eq($json['type'], 'public-key', sprintf('Unsupported type "%s"', $json['type'])); + + $id = Base64Url::decode($json['id']); + $rawId = Base64Url::decode($json['rawId']); + Assertion::true(hash_equals($id, $rawId)); + + $publicKeyCredential = new PublicKeyCredential( + $json['id'], + $json['type'], + $rawId, + $this->createResponse($json['response']) + ); + + return $publicKeyCredential; + } + + public function load(string $data): PublicKeyCredential + { + $json = json_decode($data, true); + Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); + + return $this->loadArray($json); + } + + private function createResponse(array $response): AuthenticatorResponse + { + Assertion::keyExists($response, 'clientDataJSON'); + switch (true) { + case \array_key_exists('attestationObject', $response): + $attestationObject = $this->attestationObjectLoader->load($response['attestationObject']); + + return new AuthenticatorAttestationResponse(CollectedClientData::createFormJson($response['clientDataJSON']), $attestationObject); + case \array_key_exists('authenticatorData', $response) && \array_key_exists('signature', $response): + $authData = Base64Url::decode($response['authenticatorData']); + + $authDataStream = new StringStream($authData); + $rp_id_hash = $authDataStream->read(32); + $flags = $authDataStream->read(1); + $signCount = $authDataStream->read(4); + $signCount = unpack('N', $signCount)[1]; + + $attestedCredentialData = null; + if (0 !== (\ord($flags) & self::FLAG_AT)) { + $aaguid = Uuid::fromBytes($authDataStream->read(16)); + $credentialLength = $authDataStream->read(2); + $credentialLength = unpack('n', $credentialLength)[1]; + $credentialId = $authDataStream->read($credentialLength); + $credentialPublicKey = $this->decoder->decode($authDataStream); + Assertion::isInstanceOf($credentialPublicKey, MapObject::class, 'The data does not contain a valid credential public key.'); + $attestedCredentialData = new AttestedCredentialData($aaguid, $credentialId, (string) $credentialPublicKey); + } + + $extension = null; + if (0 !== (\ord($flags) & self::FLAG_ED)) { + $extension = $this->decoder->decode($authDataStream); + $extension = AuthenticationExtensionsClientOutputsLoader::load($extension); + } + Assertion::true($authDataStream->isEOF(), 'Invalid authentication data. Presence of extra bytes.'); + $authDataStream->close(); + $authenticatorData = new AuthenticatorData($authData, $rp_id_hash, $flags, $signCount, $attestedCredentialData, $extension); + + return new AuthenticatorAssertionResponse( + CollectedClientData::createFormJson($response['clientDataJSON']), + $authenticatorData, + Base64Url::decode($response['signature']), + $response['userHandle'] ?? null + ); + default: + throw new InvalidArgumentException('Unable to create the response object'); + } + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialOptions.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialOptions.php new file mode 100644 index 00000000..e0440a47 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialOptions.php @@ -0,0 +1,61 @@ +challenge = $challenge; + $this->timeout = $timeout; + $this->extensions = $extensions ?? new AuthenticationExtensionsClientInputs(); + } + + public function getChallenge(): string + { + return $this->challenge; + } + + public function getTimeout(): ?int + { + return $this->timeout; + } + + public function getExtensions(): AuthenticationExtensionsClientInputs + { + return $this->extensions; + } + + abstract public static function createFromString(string $data): self; + + abstract public static function createFromArray(array $json): self; +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialParameters.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialParameters.php new file mode 100644 index 00000000..c0600862 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialParameters.php @@ -0,0 +1,76 @@ +type = $type; + $this->alg = $alg; + } + + public function getType(): string + { + return $this->type; + } + + public function getAlg(): int + { + return $this->alg; + } + + public static function createFromString(string $data): self + { + $data = json_decode($data, true); + Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); + Assertion::isArray($data, 'Invalid data'); + + return self::createFromArray($data); + } + + public static function createFromArray(array $json): self + { + Assertion::keyExists($json, 'type', 'Invalid input. "type" is missing.'); + Assertion::string($json['type'], 'Invalid input. "type" is not a string.'); + Assertion::keyExists($json, 'alg', 'Invalid input. "alg" is missing.'); + Assertion::integer($json['alg'], 'Invalid input. "alg" is not an integer.'); + + return new self( + $json['type'], + $json['alg'] + ); + } + + public function jsonSerialize(): array + { + return [ + 'type' => $this->type, + 'alg' => $this->alg, + ]; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialRequestOptions.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialRequestOptions.php new file mode 100644 index 00000000..1a5062d0 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialRequestOptions.php @@ -0,0 +1,127 @@ +rpId = $rpId; + $this->allowCredentials = array_values($allowCredentials); + $this->userVerification = $userVerification; + } + + public function getRpId(): ?string + { + return $this->rpId; + } + + /** + * @return PublicKeyCredentialDescriptor[] + */ + public function getAllowCredentials(): array + { + return $this->allowCredentials; + } + + public function getUserVerification(): ?string + { + return $this->userVerification; + } + + public static function createFromString(string $data): PublicKeyCredentialOptions + { + $data = json_decode($data, true); + Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); + Assertion::isArray($data, 'Invalid data'); + + return self::createFromArray($data); + } + + public static function createFromArray(array $json): PublicKeyCredentialOptions + { + Assertion::keyExists($json, 'challenge', 'Invalid input. "challenge" is missing.'); + + $allowCredentials = []; + $allowCredentialList = $json['allowCredentials'] ?? []; + foreach ($allowCredentialList as $allowCredential) { + $allowCredentials[] = PublicKeyCredentialDescriptor::createFromArray($allowCredential); + } + + return new self( + Base64Url::decode($json['challenge']), + $json['timeout'] ?? null, + $json['rpId'] ?? null, + $allowCredentials, + $json['userVerification'] ?? null, + isset($json['extensions']) ? AuthenticationExtensionsClientInputs::createFromArray($json['extensions']) : new AuthenticationExtensionsClientInputs() + ); + } + + public function jsonSerialize(): array + { + $json = [ + 'challenge' => Base64Url::encode($this->challenge), + ]; + + if (null !== $this->rpId) { + $json['rpId'] = $this->rpId; + } + + if (null !== $this->userVerification) { + $json['userVerification'] = $this->userVerification; + } + + if (0 !== \count($this->allowCredentials)) { + $json['allowCredentials'] = $this->allowCredentials; + } + + if (0 !== $this->extensions->count()) { + $json['extensions'] = $this->extensions; + } + + if (null !== $this->timeout) { + $json['timeout'] = $this->timeout; + } + + return $json; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialRpEntity.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialRpEntity.php new file mode 100644 index 00000000..e17c5d0a --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialRpEntity.php @@ -0,0 +1,56 @@ +id = $id; + } + + public function getId(): ?string + { + return $this->id; + } + + public static function createFromArray(array $json): self + { + Assertion::keyExists($json, 'name', 'Invalid input. "name" is missing.'); + + return new self( + $json['name'], + $json['id'] ?? null, + $json['icon'] ?? null + ); + } + + public function jsonSerialize(): array + { + $json = parent::jsonSerialize(); + if (null !== $this->id) { + $json['id'] = $this->id; + } + + return $json; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialSource.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialSource.php new file mode 100644 index 00000000..0bd9ab2d --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialSource.php @@ -0,0 +1,233 @@ +publicKeyCredentialId = $publicKeyCredentialId; + $this->type = $type; + $this->transports = $transports; + $this->aaguid = $aaguid; + $this->credentialPublicKey = $credentialPublicKey; + $this->userHandle = $userHandle; + $this->counter = $counter; + $this->attestationType = $attestationType; + $this->trustPath = $trustPath; + } + + /** + * @deprecated Deprecated since v2.1. Will be removed in v3.0. Please use response from the credential source returned by the AuthenticatorAttestationResponseValidator after "check" method + */ + public static function createFromPublicKeyCredential(PublicKeyCredential $publicKeyCredential, string $userHandle): self + { + $response = $publicKeyCredential->getResponse(); + Assertion::isInstanceOf($response, AuthenticatorAttestationResponse::class, 'This method is only available with public key credential containing an authenticator attestation response.'); + $publicKeyCredentialDescriptor = $publicKeyCredential->getPublicKeyCredentialDescriptor(); + $attestationStatement = $response->getAttestationObject()->getAttStmt(); + $authenticatorData = $response->getAttestationObject()->getAuthData(); + $attestedCredentialData = $authenticatorData->getAttestedCredentialData(); + Assertion::notNull($attestedCredentialData, 'No attested credential data available'); + + return new self( + $publicKeyCredentialDescriptor->getId(), + $publicKeyCredentialDescriptor->getType(), + $publicKeyCredentialDescriptor->getTransports(), + $attestationStatement->getType(), + $attestationStatement->getTrustPath(), + $attestedCredentialData->getAaguid(), + $attestedCredentialData->getCredentialPublicKey(), + $userHandle, + $authenticatorData->getSignCount() + ); + } + + public function getPublicKeyCredentialId(): string + { + return $this->publicKeyCredentialId; + } + + public function getPublicKeyCredentialDescriptor(): PublicKeyCredentialDescriptor + { + return new PublicKeyCredentialDescriptor( + $this->type, + $this->publicKeyCredentialId, + $this->transports + ); + } + + public function getAttestationType(): string + { + return $this->attestationType; + } + + public function getTrustPath(): TrustPath + { + return $this->trustPath; + } + + public function getAttestedCredentialData(): AttestedCredentialData + { + return new AttestedCredentialData( + $this->aaguid, + $this->publicKeyCredentialId, + $this->credentialPublicKey + ); + } + + public function getType(): string + { + return $this->type; + } + + /** + * @return string[] + */ + public function getTransports(): array + { + return $this->transports; + } + + public function getAaguid(): UuidInterface + { + return $this->aaguid; + } + + public function getCredentialPublicKey(): string + { + return $this->credentialPublicKey; + } + + public function getUserHandle(): string + { + return $this->userHandle; + } + + public function getCounter(): int + { + return $this->counter; + } + + public function setCounter(int $counter): void + { + $this->counter = $counter; + } + + public static function createFromArray(array $data): self + { + $keys = array_keys(get_class_vars(self::class)); + foreach ($keys as $key) { + Assertion::keyExists($data, $key, sprintf('The parameter "%s" is missing', $key)); + } + switch (true) { + case 36 === mb_strlen($data['aaguid'], '8bit'): + $uuid = Uuid::fromString($data['aaguid']); + break; + default: // Kept for compatibility with old format + $decoded = base64_decode($data['aaguid'], true); + Assertion::string($decoded, 'Invalid AAGUID'); + $uuid = Uuid::fromBytes($decoded); + } + + try { + return new self( + Base64Url::decode($data['publicKeyCredentialId']), + $data['type'], + $data['transports'], + $data['attestationType'], + TrustPathLoader::loadTrustPath($data['trustPath']), + $uuid, + Base64Url::decode($data['credentialPublicKey']), + Base64Url::decode($data['userHandle']), + $data['counter'] + ); + } catch (Throwable $throwable) { + throw new InvalidArgumentException('Unable to load the data', $throwable->getCode(), $throwable); + } + } + + public function jsonSerialize(): array + { + return [ + 'publicKeyCredentialId' => Base64Url::encode($this->publicKeyCredentialId), + 'type' => $this->type, + 'transports' => $this->transports, + 'attestationType' => $this->attestationType, + 'trustPath' => $this->trustPath, + 'aaguid' => $this->aaguid->toString(), + 'credentialPublicKey' => Base64Url::encode($this->credentialPublicKey), + 'userHandle' => Base64Url::encode($this->userHandle), + 'counter' => $this->counter, + ]; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialSourceRepository.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialSourceRepository.php new file mode 100644 index 00000000..8df9142f --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/PublicKeyCredentialSourceRepository.php @@ -0,0 +1,26 @@ +id = $id; + $this->displayName = $displayName; + } + + public function getId(): string + { + return $this->id; + } + + public function getDisplayName(): string + { + return $this->displayName; + } + + public static function createFromString(string $data): self + { + $data = json_decode($data, true); + Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); + Assertion::isArray($data, 'Invalid data'); + + return self::createFromArray($data); + } + + public static function createFromArray(array $json): self + { + Assertion::keyExists($json, 'name', 'Invalid input. "name" is missing.'); + Assertion::keyExists($json, 'id', 'Invalid input. "id" is missing.'); + Assertion::keyExists($json, 'displayName', 'Invalid input. "displayName" is missing.'); + $id = base64_decode($json['id'], true); + Assertion::string($id, 'Invalid parameter "id".'); + + return new self( + $json['name'], + $id, + $json['displayName'], + $json['icon'] ?? null + ); + } + + public function jsonSerialize(): array + { + $json = parent::jsonSerialize(); + $json['id'] = base64_encode($this->id); + $json['displayName'] = $this->displayName; + + return $json; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/Server.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/Server.php new file mode 100644 index 00000000..4580f325 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/Server.php @@ -0,0 +1,264 @@ +rpEntity = $relayingParty; + + $this->coseAlgorithmManagerFactory = new ManagerFactory(); + $this->coseAlgorithmManagerFactory->add('RS1', new RSA\RS1()); + $this->coseAlgorithmManagerFactory->add('RS256', new RSA\RS256()); + $this->coseAlgorithmManagerFactory->add('RS384', new RSA\RS384()); + $this->coseAlgorithmManagerFactory->add('RS512', new RSA\RS512()); + $this->coseAlgorithmManagerFactory->add('PS256', new RSA\PS256()); + $this->coseAlgorithmManagerFactory->add('PS384', new RSA\PS384()); + $this->coseAlgorithmManagerFactory->add('PS512', new RSA\PS512()); + $this->coseAlgorithmManagerFactory->add('ES256', new ECDSA\ES256()); + $this->coseAlgorithmManagerFactory->add('ES256K', new ECDSA\ES256K()); + $this->coseAlgorithmManagerFactory->add('ES384', new ECDSA\ES384()); + $this->coseAlgorithmManagerFactory->add('ES512', new ECDSA\ES512()); + $this->coseAlgorithmManagerFactory->add('Ed25519', new EdDSA\Ed25519()); + + $this->selectedAlgorithms = ['RS256', 'RS512', 'PS256', 'PS512', 'ES256', 'ES512', 'Ed25519']; + $this->publicKeyCredentialSourceRepository = $publicKeyCredentialSourceRepository; + $this->tokenBindingHandler = new TokenBindingNotSupportedHandler(); + $this->extensionOutputCheckerHandler = new ExtensionOutputCheckerHandler(); + $this->metadataStatementRepository = $metadataStatementRepository; + } + + /** + * @param string[] $selectedAlgorithms + */ + public function setSelectedAlgorithms(array $selectedAlgorithms): void + { + $this->selectedAlgorithms = $selectedAlgorithms; + } + + public function setTokenBindingHandler(TokenBindingNotSupportedHandler $tokenBindingHandler): void + { + $this->tokenBindingHandler = $tokenBindingHandler; + } + + public function addAlgorithm(string $alias, Algorithm $algorithm): void + { + $this->coseAlgorithmManagerFactory->add($alias, $algorithm); + $this->selectedAlgorithms[] = $alias; + $this->selectedAlgorithms = array_unique($this->selectedAlgorithms); + } + + public function setExtensionOutputCheckerHandler(ExtensionOutputCheckerHandler $extensionOutputCheckerHandler): void + { + $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler; + } + + /** + * @param PublicKeyCredentialDescriptor[] $excludedPublicKeyDescriptors + */ + public function generatePublicKeyCredentialCreationOptions(PublicKeyCredentialUserEntity $userEntity, ?string $attestationMode = PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, array $excludedPublicKeyDescriptors = [], ?AuthenticatorSelectionCriteria $criteria = null, ?AuthenticationExtensionsClientInputs $extensions = null): PublicKeyCredentialCreationOptions + { + $coseAlgorithmManager = $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms); + $publicKeyCredentialParametersList = []; + foreach ($coseAlgorithmManager->all() as $algorithm) { + $publicKeyCredentialParametersList[] = new PublicKeyCredentialParameters( + PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, + $algorithm::identifier() + ); + } + $criteria = $criteria ?? new AuthenticatorSelectionCriteria(); + $extensions = $extensions ?? new AuthenticationExtensionsClientInputs(); + $challenge = random_bytes($this->challengeSize); + + return new PublicKeyCredentialCreationOptions( + $this->rpEntity, + $userEntity, + $challenge, + $publicKeyCredentialParametersList, + $this->timeout, + $excludedPublicKeyDescriptors, + $criteria, + $attestationMode, + $extensions + ); + } + + /** + * @param PublicKeyCredentialDescriptor[] $allowedPublicKeyDescriptors + */ + public function generatePublicKeyCredentialRequestOptions(?string $userVerification = PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED, array $allowedPublicKeyDescriptors = [], ?AuthenticationExtensionsClientInputs $extensions = null): PublicKeyCredentialRequestOptions + { + return new PublicKeyCredentialRequestOptions( + random_bytes($this->challengeSize), + $this->timeout, + $this->rpEntity->getId(), + $allowedPublicKeyDescriptors, + $userVerification, + $extensions ?? new AuthenticationExtensionsClientInputs() + ); + } + + public function loadAndCheckAttestationResponse(string $data, PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, ServerRequestInterface $serverRequest): PublicKeyCredentialSource + { + $attestationStatementSupportManager = $this->getAttestationStatementSupportManager(); + $attestationObjectLoader = new AttestationObjectLoader($attestationStatementSupportManager); + $publicKeyCredentialLoader = new PublicKeyCredentialLoader($attestationObjectLoader); + + $publicKeyCredential = $publicKeyCredentialLoader->load($data); + $authenticatorResponse = $publicKeyCredential->getResponse(); + Assertion::isInstanceOf($authenticatorResponse, AuthenticatorAttestationResponse::class, 'Not an authenticator attestation response'); + + $authenticatorAttestationResponseValidator = new AuthenticatorAttestationResponseValidator( + $attestationStatementSupportManager, + $this->publicKeyCredentialSourceRepository, + $this->tokenBindingHandler, + $this->extensionOutputCheckerHandler + ); + + return $authenticatorAttestationResponseValidator->check($authenticatorResponse, $publicKeyCredentialCreationOptions, $serverRequest); + } + + public function loadAndCheckAssertionResponse(string $data, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ?PublicKeyCredentialUserEntity $userEntity, ServerRequestInterface $serverRequest): PublicKeyCredentialSource + { + $attestationStatementSupportManager = $this->getAttestationStatementSupportManager(); + $attestationObjectLoader = new AttestationObjectLoader($attestationStatementSupportManager); + $publicKeyCredentialLoader = new PublicKeyCredentialLoader($attestationObjectLoader); + + $publicKeyCredential = $publicKeyCredentialLoader->load($data); + $authenticatorResponse = $publicKeyCredential->getResponse(); + Assertion::isInstanceOf($authenticatorResponse, AuthenticatorAssertionResponse::class, 'Not an authenticator assertion response'); + + $authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator( + $this->publicKeyCredentialSourceRepository, + null, + $this->tokenBindingHandler, + $this->extensionOutputCheckerHandler, + $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms) + ); + + return $authenticatorAssertionResponseValidator->check( + $publicKeyCredential->getRawId(), + $authenticatorResponse, + $publicKeyCredentialRequestOptions, + $serverRequest, + null !== $userEntity ? $userEntity->getId() : null + ); + } + + public function enforceAndroidSafetyNetVerification(ClientInterface $client, string $apiKey, RequestFactoryInterface $requestFactory): void + { + $this->httpClient = $client; + $this->googleApiKey = $apiKey; + $this->requestFactory = $requestFactory; + } + + private function getAttestationStatementSupportManager(): AttestationStatementSupportManager + { + $attestationStatementSupportManager = new AttestationStatementSupportManager(); + $attestationStatementSupportManager->add(new NoneAttestationStatementSupport()); + if (null !== $this->metadataStatementRepository) { + $coseAlgorithmManager = $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms); + $attestationStatementSupportManager->add(new FidoU2FAttestationStatementSupport(null, $this->metadataStatementRepository)); + $attestationStatementSupportManager->add(new AndroidSafetyNetAttestationStatementSupport($this->httpClient, $this->googleApiKey, $this->requestFactory, 2000, 60000, $this->metadataStatementRepository)); + $attestationStatementSupportManager->add(new AndroidKeyAttestationStatementSupport(null, $this->metadataStatementRepository)); + $attestationStatementSupportManager->add(new TPMAttestationStatementSupport($this->metadataStatementRepository)); + $attestationStatementSupportManager->add(new PackedAttestationStatementSupport(null, $coseAlgorithmManager, $this->metadataStatementRepository)); + } + + return $attestationStatementSupportManager; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/StringStream.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/StringStream.php new file mode 100644 index 00000000..8b8c9784 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/StringStream.php @@ -0,0 +1,72 @@ +length = mb_strlen($data, '8bit'); + $resource = fopen('php://memory', 'rb+'); + Assertion::isResource($resource, 'Unable to open memory'); + $result = fwrite($resource, $data); + Assertion::integer($result, 'Unable to write memory'); + $result = rewind($resource); + Assertion::true($result, 'Unable to read memory'); + $this->data = $resource; + } + + public function read(int $length): string + { + if (0 === $length) { + return ''; + } + $read = fread($this->data, $length); + Assertion::string($read, 'Unable to read memory'); + $bytesRead = mb_strlen($read, '8bit'); + Assertion::length($read, $length, sprintf('Out of range. Expected: %d, read: %d.', $length, $bytesRead), null, '8bit'); + $this->totalRead += $bytesRead; + + return $read; + } + + public function close(): void + { + $result = fclose($this->data); + Assertion::true($result, 'Unable to close the memory'); + } + + public function isEOF(): bool + { + return $this->totalRead === $this->length; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TokenBinding/IgnoreTokenBindingHandler.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TokenBinding/IgnoreTokenBindingHandler.php new file mode 100644 index 00000000..fe03da7e --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TokenBinding/IgnoreTokenBindingHandler.php @@ -0,0 +1,24 @@ +status = $status; + $this->id = $id; + } + + public static function createFormArray(array $json): self + { + Assertion::keyExists($json, 'status', 'The member "status" is required'); + $status = $json['status']; + Assertion::inArray( + $status, + self::getSupportedStatus(), + sprintf('The member "status" is invalid. Supported values are: %s', implode(', ', self::getSupportedStatus())) + ); + $id = \array_key_exists('id', $json) ? Base64Url::decode($json['id']) : null; + + return new self($status, $id); + } + + public function getStatus(): string + { + return $this->status; + } + + public function getId(): ?string + { + return $this->id; + } + + /** + * @return string[] + */ + private static function getSupportedStatus(): array + { + return [ + self::TOKEN_BINDING_STATUS_PRESENT, + self::TOKEN_BINDING_STATUS_SUPPORTED, + self::TOKEN_BINDING_STATUS_NOT_SUPPORTED, + ]; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TokenBinding/TokenBindingHandler.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TokenBinding/TokenBindingHandler.php new file mode 100644 index 00000000..803d82e3 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TokenBinding/TokenBindingHandler.php @@ -0,0 +1,21 @@ +getStatus(), 'Token binding not supported.'); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/CertificateTrustPath.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/CertificateTrustPath.php new file mode 100644 index 00000000..17537159 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/CertificateTrustPath.php @@ -0,0 +1,55 @@ +certificates = $certificates; + } + + /** + * @return string[] + */ + public function getCertificates(): array + { + return $this->certificates; + } + + public static function createFromArray(array $data): TrustPath + { + Assertion::keyExists($data, 'x5c', 'The trust path type is invalid'); + + return new CertificateTrustPath($data['x5c']); + } + + public function jsonSerialize(): array + { + return [ + 'type' => self::class, + 'x5c' => $this->certificates, + ]; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/EcdaaKeyIdTrustPath.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/EcdaaKeyIdTrustPath.php new file mode 100644 index 00000000..7656cb75 --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/EcdaaKeyIdTrustPath.php @@ -0,0 +1,49 @@ +ecdaaKeyId = $ecdaaKeyId; + } + + public function getEcdaaKeyId(): string + { + return $this->ecdaaKeyId; + } + + public function jsonSerialize(): array + { + return [ + 'type' => self::class, + 'ecdaaKeyId' => $this->ecdaaKeyId, + ]; + } + + public static function createFromArray(array $data): TrustPath + { + Assertion::keyExists($data, 'ecdaaKeyId', 'The trust path type is invalid'); + + return new EcdaaKeyIdTrustPath($data['ecdaaKeyId']); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/EmptyTrustPath.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/EmptyTrustPath.php new file mode 100644 index 00000000..d94ff09c --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/EmptyTrustPath.php @@ -0,0 +1,29 @@ + self::class, + ]; + } + + public static function createFromArray(array $data): TrustPath + { + return new EmptyTrustPath(); + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/TrustPath.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/TrustPath.php new file mode 100644 index 00000000..b180d89f --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/TrustPath/TrustPath.php @@ -0,0 +1,21 @@ + EmptyTrustPath::class, + 'ecdaa_key_id' => EcdaaKeyIdTrustPath::class, + 'x5c' => CertificateTrustPath::class, + ]; + } +} diff --git a/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/Util/CoseSignatureFixer.php b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/Util/CoseSignatureFixer.php new file mode 100644 index 00000000..5418a87d --- /dev/null +++ b/lam/lib/3rdParty/composer/web-auth/webauthn-lib/src/Util/CoseSignatureFixer.php @@ -0,0 +1,54 @@ + TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA, 'YubiKey' => TwoFactorProviderService::TWO_FACTOR_YUBICO, 'Duo' => TwoFactorProviderService::TWO_FACTOR_DUO, - 'Webauthn' => TwoFactorProviderService::TWO_FACTOR_DUO, - 'Webauthn' => TwoFactorProviderService::TWO_FACTOR_WEBAUTHN, ); + if (version_compare(phpversion(), '7.2.0') >= 0) { + $twoFactorOptions['Webauthn'] = TwoFactorProviderService::TWO_FACTOR_WEBAUTHN; + } $twoFactorSelect = new htmlResponsiveSelect('twoFactor', $twoFactorOptions, array($conf->getTwoFactorAuthentication()), _('Provider'), '514'); $twoFactorSelect->setHasDescriptiveElements(true); $twoFactorSelect->setTableRowsToHide(array( diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index 1413f807..6452f71c 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -1396,12 +1396,42 @@ window.lam.webauthn.run = function(prefix) { }) .done(function(jsonData) { console.log(jsonData); + if (jsonData.action === 'register') { + window.lam.webauthn.register(jsonData.registration); + } }) .fail(function() { console.log('Webauthn failed'); }); } +/** + * Performs a webauthn registration. + * + * @param publicKey registration object + */ +window.lam.webauthn.register = function(publicKey) { + console.log(publicKey); + publicKey.challenge = Uint8Array.from(window.atob(publicKey.challenge), c=>c.charCodeAt(0)); + publicKey.user.id = Uint8Array.from(window.atob(publicKey.user.id), c=>c.charCodeAt(0)); + navigator.credentials.create({publicKey}) + .then(function (data) { + console.log(data); + let publicKeyCredential = { + id: data.id, + type: data.type, + rawId: btoa(String.fromCharCode(new Uint8Array(data.rawId))), + response: { + clientDataJSON: btoa(String.fromCharCode((new Uint8Array(data.response.clientDataJSON)))), + attestationObject: btoa(String.fromCharCode((new Uint8Array(data.response.attestationObject)))) + } + }; + console.log(publicKeyCredential); + //window.location = '/request_post?data='+btoa(JSON.stringify(publicKeyCredential)); + }, function (error) { + console.log(error); + }); +} jQuery(document).ready(function() { window.lam.gui.equalHeight(); diff --git a/lam/templates/misc/ajax.php b/lam/templates/misc/ajax.php index 09383d35..de69266c 100644 --- a/lam/templates/misc/ajax.php +++ b/lam/templates/misc/ajax.php @@ -7,6 +7,9 @@ use \htmlResponsiveRow; use \htmlLink; use \htmlOutputText; use \htmlButton; +use function LAM\LOGIN\WEBAUTHN\getRegistrationObject; +use function LAM\LOGIN\WEBAUTHN\isRegistered; + /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) @@ -71,7 +74,7 @@ class Ajax { $this->setHeader(); // check token validateSecurityToken(); - + $isSelfService = isset($_GET['selfservice']); if (isset($_GET['module']) && isset($_GET['scope']) && in_array($_GET['module'], getAvailableModules($_GET['scope']))) { enforceUserIsLoggedIn(); if (isset($_GET['useContainer']) && ($_GET['useContainer'] == '1')) { @@ -103,7 +106,7 @@ class Ajax { } if ($function === 'webauthn') { enforceUserIsLoggedIn(false); - $this->manageWebauthn(); + $this->manageWebauthn($isSelfService); die(); } enforceUserIsLoggedIn(); @@ -184,9 +187,25 @@ class Ajax { /** * Manages webauthn requests. + * + * @param bool $isSelfService request is from self service */ - private function manageWebauthn() { + private function manageWebauthn($isSelfService) { + include_once __DIR__ . '/../../lib/3rdParty/composer/autoload.php'; + include_once __DIR__ . '/../../lib/webauthn.inc'; $userDN = $_SESSION['ldap']->getUserName(); + $isRegistered = isRegistered($userDN); + if (!$isRegistered) { + $registrationObject = getRegistrationObject($userDN); + echo json_encode( + array( + 'action' => 'register', + 'registration' => $registrationObject + ), + JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE + ); + die(); + } } /** From 1535bf4da645f355a8d41a3fbaa649373e10fd42 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Mon, 25 Nov 2019 21:07:23 +0100 Subject: [PATCH 09/51] webauthn --- lam/help/help.inc | 2 ++ lam/lib/config.inc | 24 ++++++++++++++++++++++- lam/lib/webauthn.inc | 17 +++++++++++----- lam/templates/config/confmain.php | 32 +++++++++++++++++-------------- lam/templates/lib/500_lam.js | 16 ++++++++++++---- lam/templates/misc/ajax.php | 2 +- lam/tests/lib/LAMConfigTest.php | 11 +++++++++++ 7 files changed, 79 insertions(+), 25 deletions(-) diff --git a/lam/help/help.inc b/lam/help/help.inc index 89f8b3ad..b8f44924 100644 --- a/lam/help/help.inc +++ b/lam/help/help.inc @@ -329,6 +329,8 @@ $helpArray = array ( "Text" => _('This text is displayed as footer on the self service main page.')), "528" => array ("Headline" => _('User name attribute'), "Text" => _('The attribute (e.g. "uid") that contains the user name for the 2-factor service.')), + "529" => array ("Headline" => _('Domain'), + "Text" => _('Please enter the webauthn domain. This is the public domain of the webserver (e.g. "example.com"). Do not include protocol or port.')), "550" => array ("Headline" => _("From address"), "Text" => _("This email address will be set as sender address of all password mails. If empty the system default (php.ini) will be used.")), "551" => array ("Headline" => _("Subject"), diff --git a/lam/lib/config.inc b/lam/lib/config.inc index 076b006a..ec339107 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -607,6 +607,7 @@ class LAMConfig { private $twoFactorAuthenticationURL = 'https://localhost'; private $twoFactorAuthenticationClientId = null; private $twoFactorAuthenticationSecretKey = null; + private $twoFactorAuthenticationDomain = null; private $twoFactorAuthenticationInsecure = false; private $twoFactorAuthenticationLabel = null; private $twoFactorAuthenticationOptional = false; @@ -625,7 +626,7 @@ class LAMConfig { 'scriptUserName', 'scriptSSHKey', 'scriptSSHKeyPassword', 'twoFactorAuthentication', 'twoFactorAuthenticationURL', 'twoFactorAuthenticationInsecure', 'twoFactorAuthenticationLabel', 'twoFactorAuthenticationOptional', 'twoFactorAuthenticationCaption', 'twoFactorAuthenticationClientId', 'twoFactorAuthenticationSecretKey', - 'twoFactorAuthenticationAttribute', 'referentialIntegrityOverlay' + 'twoFactorAuthenticationDomain', 'twoFactorAuthenticationAttribute', 'referentialIntegrityOverlay' ); @@ -991,6 +992,9 @@ class LAMConfig { if (!in_array("twoFactorAuthenticationSecretKey", $saved)) { array_push($file_array, "\n" . "twoFactorAuthenticationSecretKey: " . $this->twoFactorAuthenticationSecretKey . "\n"); } + if (!in_array("twoFactorAuthenticationDomain", $saved)) { + array_push($file_array, "\n" . "twoFactorAuthenticationDomain: " . $this->twoFactorAuthenticationDomain . "\n"); + } if (!in_array("twoFactorAuthenticationInsecure", $saved)) { array_push($file_array, "\n" . "twoFactorAuthenticationInsecure: " . $this->twoFactorAuthenticationInsecure . "\n"); } @@ -2416,6 +2420,24 @@ class LAMConfig { return $this->twoFactorAuthenticationSecretKey; } + /** + * Sets the domain. + * + * @param string $domain domain + */ + public function setTwoFactorAuthenticationDomain($domain) { + $this->twoFactorAuthenticationDomain = $domain; + } + + /** + * Returns the domain. + * + * @return string domain + */ + public function getTwoFactorAuthenticationDomain() { + return $this->twoFactorAuthenticationDomain; + } + /** * Returns if SSL certificate verification is turned off. * diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index 9828b431..283eb0f6 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -51,10 +51,11 @@ function isRegistered($dn) { * Returns a challenge for a new token. * * @param string $dn DN + * @param bool $isSelfService is executed in self service * @return PublicKeyCredentialCreationOptions challenge */ -function getRegistrationObject($dn) { - $rpEntity = createRpEntry(); +function getRegistrationObject($dn, $isSelfService) { + $rpEntity = createRpEntry($isSelfService); $userEntity = getUserEntity($dn); $challenge = generateRandomPassword(32); $credentialParameters = getCredentialParameters(); @@ -76,13 +77,19 @@ function getRegistrationObject($dn) { /** * Returns the part that identifies the server and application. * + * @param bool $isSelfService is executed in self service * @return PublicKeyCredentialRpEntity relying party entry */ -function createRpEntry() { +function createRpEntry($isSelfService) { + $pathPrefix = $isSelfService ? '../' : ''; + $icon = $pathPrefix . '../graphics/logo136.png'; + if (!$isSelfService) { + $domain = $_SESSION['config']->getTwoFactorAuthenticationDomain(); + } return new PublicKeyCredentialRpEntity( 'LDAP Account Manager', //Name - null, // TODO check if domain is required - null // TODO icon + $domain, + $icon ); } diff --git a/lam/templates/config/confmain.php b/lam/templates/config/confmain.php index 68729b58..a99f2d5e 100644 --- a/lam/templates/config/confmain.php +++ b/lam/templates/config/confmain.php @@ -473,21 +473,22 @@ if (extension_loaded('curl')) { $twoFactorSelect = new htmlResponsiveSelect('twoFactor', $twoFactorOptions, array($conf->getTwoFactorAuthentication()), _('Provider'), '514'); $twoFactorSelect->setHasDescriptiveElements(true); $twoFactorSelect->setTableRowsToHide(array( - TwoFactorProviderService::TWO_FACTOR_NONE => array('twoFactorURL', 'twoFactorURLs', 'twoFactorInsecure', 'twoFactorLabel', - 'twoFactorOptional', 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey', 'twoFactorAttribute'), - TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA => array('twoFactorURLs', 'twoFactorClientId', 'twoFactorSecretKey'), - TwoFactorProviderService::TWO_FACTOR_YUBICO => array('twoFactorURL', 'twoFactorAttribute'), - TwoFactorProviderService::TWO_FACTOR_DUO => array('twoFactorURLs', 'twoFactorOptional', 'twoFactorInsecure', 'twoFactorLabel'), - TwoFactorProviderService::TWO_FACTOR_WEBAUTHN => array('twoFactorURL', 'twoFactorURLs', 'twoFactorInsecure', 'twoFactorLabel', - 'twoFactorOptional', 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey', 'twoFactorAttribute'), + TwoFactorProviderService::TWO_FACTOR_NONE => array('twoFactorURL', 'twoFactorURLs', 'twoFactorInsecure', 'twoFactorLabel', + 'twoFactorOptional', 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey', 'twoFactorAttribute', 'twoFactorDomain'), + TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA => array('twoFactorURLs', 'twoFactorClientId', 'twoFactorSecretKey', 'twoFactorDomain'), + TwoFactorProviderService::TWO_FACTOR_YUBICO => array('twoFactorURL', 'twoFactorAttribute', 'twoFactorDomain'), + TwoFactorProviderService::TWO_FACTOR_DUO => array('twoFactorURLs', 'twoFactorOptional', 'twoFactorInsecure', 'twoFactorLabel', 'twoFactorDomain'), + TwoFactorProviderService::TWO_FACTOR_WEBAUTHN => array('twoFactorURL', 'twoFactorURLs', 'twoFactorInsecure', 'twoFactorLabel', + 'twoFactorOptional', 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey', 'twoFactorAttribute'), )); $twoFactorSelect->setTableRowsToShow(array( - TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA => array('twoFactorURL', 'twoFactorInsecure', 'twoFactorLabel', - 'twoFactorOptional', 'twoFactorCaption', 'twoFactorAttribute'), - TwoFactorProviderService::TWO_FACTOR_YUBICO => array('twoFactorURLs', 'twoFactorInsecure', 'twoFactorLabel', - 'twoFactorOptional', 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey'), - TwoFactorProviderService::TWO_FACTOR_DUO => array('twoFactorURL', 'twoFactorLabel', - 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey', 'twoFactorAttribute'), + TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA => array('twoFactorURL', 'twoFactorInsecure', 'twoFactorLabel', + 'twoFactorOptional', 'twoFactorCaption', 'twoFactorAttribute'), + TwoFactorProviderService::TWO_FACTOR_YUBICO => array('twoFactorURLs', 'twoFactorInsecure', 'twoFactorLabel', + 'twoFactorOptional', 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey'), + TwoFactorProviderService::TWO_FACTOR_DUO => array('twoFactorURL', 'twoFactorLabel', + 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey', 'twoFactorAttribute'), + TwoFactorProviderService::TWO_FACTOR_WEBAUTHN => array('twoFactorDomain') )); $row->add($twoFactorSelect, 12); $twoFactorAttribute = new htmlResponsiveInputField(_("User name attribute"), 'twoFactorAttribute', $conf->getTwoFactorAuthenticationAttribute(), '528'); @@ -500,8 +501,10 @@ if (extension_loaded('curl')) { $row->add($twoFactorUrl, 12); $twoFactorClientId = new htmlResponsiveInputField(_("Client id"), 'twoFactorClientId', $conf->getTwoFactorAuthenticationClientId(), '524'); $row->add($twoFactorClientId, 12); - $twoFactorSecretKey = new htmlResponsiveInputField(_("Secret key"), 'twoFactorSecretKey', $conf->getTwoFactorAuthenticationSecretKey(), '525'); + $twoFactorSecretKey = new htmlResponsiveInputField(_("Secret key"), 'twoFactorSecretKey', $conf->getTwoFactorAuthenticationSecretKey(), '528'); $row->add($twoFactorSecretKey, 12); + $twoFactorDomain = new htmlResponsiveInputField(_("Domain"), 'twoFactorDomain', $conf->getTwoFactorAuthenticationDomain(), '529'); + $row->add($twoFactorDomain, 12); $twoFactorLabel = new htmlResponsiveInputField(_("Label"), 'twoFactorLabel', $conf->getTwoFactorAuthenticationLabel(), '517'); $row->add($twoFactorLabel, 12); $row->add(new htmlResponsiveInputCheckbox('twoFactorOptional', $conf->getTwoFactorAuthenticationOptional(), _('Optional'), '519'), 12); @@ -741,6 +744,7 @@ function checkInput() { } $conf->setTwoFactorAuthenticationClientId($_POST['twoFactorClientId']); $conf->setTwoFactorAuthenticationSecretKey($_POST['twoFactorSecretKey']); + $conf->setTwoFactorAuthenticationDomain($_POST['twoFactorDomain']); $conf->setTwoFactorAuthenticationInsecure(isset($_POST['twoFactorInsecure']) && ($_POST['twoFactorInsecure'] == 'on')); $conf->setTwoFactorAuthenticationLabel($_POST['twoFactorLabel']); $conf->setTwoFactorAuthenticationOptional(isset($_POST['twoFactorOptional']) && ($_POST['twoFactorOptional'] == 'on')); diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index 6452f71c..e30787e1 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -1411,19 +1411,17 @@ window.lam.webauthn.run = function(prefix) { * @param publicKey registration object */ window.lam.webauthn.register = function(publicKey) { - console.log(publicKey); publicKey.challenge = Uint8Array.from(window.atob(publicKey.challenge), c=>c.charCodeAt(0)); publicKey.user.id = Uint8Array.from(window.atob(publicKey.user.id), c=>c.charCodeAt(0)); navigator.credentials.create({publicKey}) .then(function (data) { - console.log(data); let publicKeyCredential = { id: data.id, type: data.type, rawId: btoa(String.fromCharCode(new Uint8Array(data.rawId))), response: { - clientDataJSON: btoa(String.fromCharCode((new Uint8Array(data.response.clientDataJSON)))), - attestationObject: btoa(String.fromCharCode((new Uint8Array(data.response.attestationObject)))) + clientDataJSON: window.lam.webauthn.arrayToBase64String(new Uint8Array(data.response.clientDataJSON)), + attestationObject: window.lam.webauthn.arrayToBase64String(new Uint8Array(data.response.attestationObject)) } }; console.log(publicKeyCredential); @@ -1433,6 +1431,16 @@ window.lam.webauthn.register = function(publicKey) { }); } +/** + * Converts an array to a base64 string. + * + * @param input array + * @returns base64 string + */ +window.lam.webauthn.arrayToBase64String = function(input) { + return btoa(String.fromCharCode(...input)); +} + jQuery(document).ready(function() { window.lam.gui.equalHeight(); window.lam.form.autoTrim(); diff --git a/lam/templates/misc/ajax.php b/lam/templates/misc/ajax.php index de69266c..c88f3fe9 100644 --- a/lam/templates/misc/ajax.php +++ b/lam/templates/misc/ajax.php @@ -196,7 +196,7 @@ class Ajax { $userDN = $_SESSION['ldap']->getUserName(); $isRegistered = isRegistered($userDN); if (!$isRegistered) { - $registrationObject = getRegistrationObject($userDN); + $registrationObject = getRegistrationObject($userDN, $isSelfService); echo json_encode( array( 'action' => 'register', diff --git a/lam/tests/lib/LAMConfigTest.php b/lam/tests/lib/LAMConfigTest.php index 6d0c2738..e946d72d 100644 --- a/lam/tests/lib/LAMConfigTest.php +++ b/lam/tests/lib/LAMConfigTest.php @@ -565,6 +565,17 @@ class LAMConfigTest extends PHPUnit_Framework_TestCase { $this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationSecretKey()); } + /** + * Tests LAMConfig->getTwoFactorAuthenticationDomain() and LAMConfig->setTwoFactorAuthenticationDomain() + */ + public function testTwoFactorAuthenticationDomain() { + $val = 'test.com'; + $this->lAMConfig->setTwoFactorAuthenticationDomain($val); + $this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationDomain()); + $this->doSave(); + $this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationDomain()); + } + /** * Tests LAMConfig->getTwoFactorAuthenticationInsecure() and LAMConfig->setTwoFactorAuthenticationInsecure() */ From 58e15da1a856809715b695d320a2b838625ed2a1 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 28 Nov 2019 21:18:25 +0100 Subject: [PATCH 10/51] updated test cases --- lam/tests/lib/3rdParty/pla/functionsTest.php | 5 ++-- lam/tests/lib/LAMConfigTest.php | 4 +-- lam/tests/lib/accountTest.php | 3 ++- lam/tests/lib/baseModuleTest.php | 3 ++- lam/tests/lib/importTest.php | 25 ++++++++++--------- lam/tests/lib/modules/bindDLZTest.php | 5 ++-- lam/tests/lib/modules/customFieldsTest.php | 5 ++-- lam/tests/lib/modules/ppolicyUserTest.php | 5 ++-- lam/tests/lib/modules/sambaSamAccountTest.php | 6 ++--- lam/tests/lib/modules/shadowAccountTest.php | 7 +++--- lam/tests/lib/modules/sudoRoleTest.php | 6 ++--- lam/tests/lib/modules/windowsUserTest.php | 6 ++--- lam/tests/lib/pdfstructTest.php | 3 ++- lam/tests/lib/securityTest.php | 4 +-- lam/tests/lib/selfServiceTest.php | 3 ++- lam/tests/lib/typesTest.php | 6 ++--- 16 files changed, 53 insertions(+), 43 deletions(-) diff --git a/lam/tests/lib/3rdParty/pla/functionsTest.php b/lam/tests/lib/3rdParty/pla/functionsTest.php index dbc95c61..28405370 100644 --- a/lam/tests/lib/3rdParty/pla/functionsTest.php +++ b/lam/tests/lib/3rdParty/pla/functionsTest.php @@ -1,8 +1,9 @@ setExpectedException(LAMException::class, 'this is no LDIF'); + $this->expectException(LAMException::class, 'this is no LDIF'); $importer = new Importer(); $importer->getTasks($lines); @@ -59,7 +60,7 @@ class ImporterTest extends PHPUnit_Framework_TestCase { "version: 3" ); - $this->setExpectedException(LAMException::class, 'version: 3'); + $this->expectException(LAMException::class, 'version: 3'); $importer = new Importer(); $importer->getTasks($lines); @@ -75,7 +76,7 @@ class ImporterTest extends PHPUnit_Framework_TestCase { "version: 1" ); - $this->setExpectedException(LAMException::class); + $this->expectException(LAMException::class); $importer = new Importer(); $importer->getTasks($lines); @@ -90,7 +91,7 @@ class ImporterTest extends PHPUnit_Framework_TestCase { "some: data" ); - $this->setExpectedException(LAMException::class); + $this->expectException(LAMException::class); $importer = new Importer(); $importer->getTasks($lines); @@ -106,7 +107,7 @@ class ImporterTest extends PHPUnit_Framework_TestCase { "dn: uid=test,dc=example,dc=com" ); - $this->setExpectedException(LAMException::class, 'dn: uid=test,dc=example,dc=com'); + $this->expectException(LAMException::class, 'dn: uid=test,dc=example,dc=com'); $importer = new Importer(); $importer->getTasks($lines); @@ -141,7 +142,7 @@ class ImporterTest extends PHPUnit_Framework_TestCase { "uid: test", ); - $this->setExpectedException(LAMException::class, 'uid=test,dc=example,dc=com - changeType: invalid'); + $this->expectException(LAMException::class, 'uid=test,dc=example,dc=com - changeType: invalid'); $importer = new Importer(); $tasks = $importer->getTasks($lines); @@ -178,7 +179,7 @@ class ImporterTest extends PHPUnit_Framework_TestCase { "uid: test", ); - $this->setExpectedException(LAMException::class, 'uid=test,dc=example,dc=com'); + $this->expectException(LAMException::class, 'uid=test,dc=example,dc=com'); $importer = new Importer(); $tasks = $importer->getTasks($lines); @@ -197,7 +198,7 @@ class ImporterTest extends PHPUnit_Framework_TestCase { "deleteoldrdn: x", ); - $this->setExpectedException(LAMException::class, 'uid=test,dc=example,dc=com'); + $this->expectException(LAMException::class, 'uid=test,dc=example,dc=com'); $importer = new Importer(); $tasks = $importer->getTasks($lines); @@ -235,7 +236,7 @@ class ImporterTest extends PHPUnit_Framework_TestCase { "uid: test", ); - $this->setExpectedException(LAMException::class, 'uid=test,dc=example,dc=com'); + $this->expectException(LAMException::class, 'uid=test,dc=example,dc=com'); $importer = new Importer(); $tasks = $importer->getTasks($lines); @@ -271,7 +272,7 @@ class ImporterTest extends PHPUnit_Framework_TestCase { "invalid: test", ); - $this->setExpectedException(LAMException::class, 'uid=test,dc=example,dc=com'); + $this->expectException(LAMException::class, 'uid=test,dc=example,dc=com'); $importer = new Importer(); $tasks = $importer->getTasks($lines); @@ -291,7 +292,7 @@ class ImporterTest extends PHPUnit_Framework_TestCase { "invalid: uid2" ); - $this->setExpectedException(LAMException::class, 'uid=test,dc=example,dc=com'); + $this->expectException(LAMException::class, 'uid=test,dc=example,dc=com'); $importer = new Importer(); $tasks = $importer->getTasks($lines); diff --git a/lam/tests/lib/modules/bindDLZTest.php b/lam/tests/lib/modules/bindDLZTest.php index c9dfb8a6..81e5f7be 100644 --- a/lam/tests/lib/modules/bindDLZTest.php +++ b/lam/tests/lib/modules/bindDLZTest.php @@ -1,7 +1,8 @@ assertEquals('1', bindDLZ::increaseSerial('')); diff --git a/lam/tests/lib/modules/customFieldsTest.php b/lam/tests/lib/modules/customFieldsTest.php index 108b217b..0932cd91 100644 --- a/lam/tests/lib/modules/customFieldsTest.php +++ b/lam/tests/lib/modules/customFieldsTest.php @@ -1,7 +1,8 @@ assertFalse(sambaSamAccount::validateHistoryEntry("password", "")); diff --git a/lam/tests/lib/modules/shadowAccountTest.php b/lam/tests/lib/modules/shadowAccountTest.php index bcf49312..2fb8b13c 100644 --- a/lam/tests/lib/modules/shadowAccountTest.php +++ b/lam/tests/lib/modules/shadowAccountTest.php @@ -1,8 +1,9 @@ array('shadowAccount')); @@ -122,7 +123,7 @@ if (is_readable('lam/lib/passwordExpirationJob.inc')) { * @author Roland Gruber * */ - class ShadowAccountPasswordNotifyJobTest extends PHPUnit_Framework_TestCase { + class ShadowAccountPasswordNotifyJobTest extends TestCase { private $job; diff --git a/lam/tests/lib/modules/sudoRoleTest.php b/lam/tests/lib/modules/sudoRoleTest.php index 56630701..e81ffb96 100644 --- a/lam/tests/lib/modules/sudoRoleTest.php +++ b/lam/tests/lib/modules/sudoRoleTest.php @@ -1,9 +1,9 @@ array('user')); diff --git a/lam/tests/lib/pdfstructTest.php b/lam/tests/lib/pdfstructTest.php index 7e206065..bf0ba93d 100644 --- a/lam/tests/lib/pdfstructTest.php +++ b/lam/tests/lib/pdfstructTest.php @@ -4,6 +4,7 @@ use LAM\PDF\PDFEntrySection; use LAM\PDF\PDFStructureReader; use LAM\PDF\PDFStructure; use LAM\PDF\PDFStructureWriter; +use PHPUnit\Framework\TestCase; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) @@ -33,7 +34,7 @@ include_once 'lam/lib/pdfstruct.inc'; * @author Roland Gruber * */ -class ReadStructureTest extends PHPUnit_Framework_TestCase { +class ReadStructureTest extends TestCase { /** * Reads the sample structure. diff --git a/lam/tests/lib/securityTest.php b/lam/tests/lib/securityTest.php index 9fcf4e38..2a5cf4a1 100644 --- a/lam/tests/lib/securityTest.php +++ b/lam/tests/lib/securityTest.php @@ -1,6 +1,6 @@ Date: Thu, 28 Nov 2019 21:19:44 +0100 Subject: [PATCH 11/51] webauthn --- lam/lib/2factor.inc | 19 ++++----- lam/lib/webauthn.inc | 71 +++++++++++++++++++++++++++++++++- lam/templates/lib/500_lam.js | 6 ++- lam/templates/login2Factor.php | 2 +- lam/templates/misc/ajax.php | 1 + 5 files changed, 84 insertions(+), 15 deletions(-) diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index a4a3f976..72862e11 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -10,6 +10,7 @@ use \htmlJavaScript; use \htmlStatusMessage; use \htmlDiv; use \LAMException; +use function LAM\LOGIN\WEBAUTHN\storeNewRegistration; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) @@ -460,7 +461,7 @@ class DuoProvider extends BaseProvider { * @see \LAM\LIB\TWO_FACTOR\TwoFactorProvider::verify2ndFactor() */ public function verify2ndFactor($user, $password, $serial, $twoFactorInput) { - logNewMessage(LOG_DEBUG, 'PrivacyIDEAProvider: Checking 2nd factor for ' . $user); + logNewMessage(LOG_DEBUG, 'DuoProvider: Checking 2nd factor for ' . $user); $loginAttribute = $this->getLoginAttributeValue($user); $response = $_POST['sig_response']; include_once(__DIR__ . "/3rdParty/duo/Web.php"); @@ -540,19 +541,15 @@ class WebauthnProvider extends BaseProvider { * @see \LAM\LIB\TWO_FACTOR\TwoFactorProvider::verify2ndFactor() */ public function verify2ndFactor($user, $password, $serial, $twoFactorInput) { - logNewMessage(LOG_DEBUG, 'PrivacyIDEAProvider: Checking 2nd factor for ' . $user); - $loginAttribute = $this->getLoginAttributeValue($user); + logNewMessage(LOG_DEBUG, 'WebauthnProvider: Checking 2nd factor for ' . $user); $response = $_POST['sig_response']; - include_once(__DIR__ . "/3rdParty/duo/Web.php"); - $result = \Duo\Web::verifyResponse( - $this->config->twoFactorAuthenticationClientId, - $this->config->twoFactorAuthenticationSecretKey, - $this->getAKey(), - $response); - if ($result === $loginAttribute) { + include_once __DIR__ . '/3rdParty/composer/autoload.php'; + include_once __DIR__ . '/webauthn.inc'; + $registrationObject = $_SESSION['webauthn_registration']; + if (storeNewRegistration($registrationObject, $response)) { return true; } - logNewMessage(LOG_ERR, 'DUO authentication failed'); + logNewMessage(LOG_ERR, 'Webauthn authentication failed'); return false; } diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index 283eb0f6..364bdae1 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -2,13 +2,34 @@ namespace LAM\LOGIN\WEBAUTHN; +use CBOR\Decoder; +use CBOR\OtherObject\OtherObjectManager; +use CBOR\Tag\TagObjectManager; +use Cose\Algorithm\Manager; +use Cose\Algorithm\Signature\ECDSA\ES256; +use Cose\Algorithm\Signature\ECDSA\ES384; +use Cose\Algorithm\Signature\ECDSA\ES512; +use Cose\Algorithm\Signature\EdDSA\EdDSA; +use Cose\Algorithm\Signature\RSA\RS1; +use Cose\Algorithm\Signature\RSA\RS256; +use Cose\Algorithm\Signature\RSA\RS384; +use Cose\Algorithm\Signature\RSA\RS512; use \Cose\Algorithms; +use http\Client; +use Webauthn\AttestationStatement\AndroidKeyAttestationStatementSupport; +use Webauthn\AttestationStatement\AndroidSafetyNetAttestationStatementSupport; +use Webauthn\AttestationStatement\AttestationStatementSupportManager; +use Webauthn\AttestationStatement\FidoU2FAttestationStatementSupport; +use Webauthn\AttestationStatement\NoneAttestationStatementSupport; +use Webauthn\AttestationStatement\PackedAttestationStatementSupport; +use Webauthn\AttestationStatement\TPMAttestationStatementSupport; use \Webauthn\PublicKeyCredentialCreationOptions; use \Webauthn\PublicKeyCredentialRpEntity; use \Webauthn\PublicKeyCredentialParameters; use \Webauthn\PublicKeyCredentialUserEntity; use \Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; use \Webauthn\AuthenticatorSelectionCriteria; +use Webauthn\TokenBinding\IgnoreTokenBindingHandler; /* @@ -52,7 +73,7 @@ function isRegistered($dn) { * * @param string $dn DN * @param bool $isSelfService is executed in self service - * @return PublicKeyCredentialCreationOptions challenge + * @return PublicKeyCredentialCreationOptions registration object */ function getRegistrationObject($dn, $isSelfService) { $rpEntity = createRpEntry($isSelfService); @@ -120,3 +141,51 @@ function getCredentialParameters() { ); } +/** + * Verifies the registration and stores it in the database. + * + * @param PublicKeyCredentialCreationOptions $registration registration object + * @param $clientResponse client response + * @return bool true if response is valid and registration succeeded + */ +function storeNewRegistration($registration, $clientResponse) { + $decoder = getCborDecoder(); + $tokenBindingHandler = new IgnoreTokenBindingHandler(); + $attestationSupportManager = getAttestationSupportManager($decoder); + return false; +} + +/** + * Returns a CBOR decoder. + * + * @return Decoder decoder + */ +function getCborDecoder() { + return new Decoder(new TagObjectManager(), new OtherObjectManager()); +} + +/** + * Creates the attestation support manager. + * + * @param Decoder $decoder decoder + * @return AttestationStatementSupportManager manager + */ +function getAttestationSupportManager($decoder) { + $manager = new AttestationStatementSupportManager(); + $manager->add(new NoneAttestationStatementSupport()); + $manager->add(new FidoU2FAttestationStatementSupport()); + $manager->add(new AndroidSafetyNetAttestationStatementSupport(new Client() /*TODO*/)); + $manager->add(new AndroidKeyAttestationStatementSupport($decoder)); + $manager->add(new TPMAttestationStatementSupport()); + $coseManager = new Manager(); + $coseManager->add(new ES256()); + $coseManager->add(new ES384()); + $coseManager->add(new ES512()); + $coseManager->add(new EdDSA()); + $coseManager->add(new RS1()); + $coseManager->add(new RS256()); + $coseManager->add(new RS384); + $coseManager->add(new RS512()); + $manager->add(new PackedAttestationStatementSupport($decoder, $coseManager)); + return $manager; +} diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index e30787e1..9233031a 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -1424,8 +1424,10 @@ window.lam.webauthn.register = function(publicKey) { attestationObject: window.lam.webauthn.arrayToBase64String(new Uint8Array(data.response.attestationObject)) } }; - console.log(publicKeyCredential); - //window.location = '/request_post?data='+btoa(JSON.stringify(publicKeyCredential)); + let form = jQuery("#2faform"); + let response = btoa(JSON.stringify(publicKeyCredential)); + form.append(''); + form.submit(); }, function (error) { console.log(error); }); diff --git a/lam/templates/login2Factor.php b/lam/templates/login2Factor.php index 5182c7c0..e36e5edc 100644 --- a/lam/templates/login2Factor.php +++ b/lam/templates/login2Factor.php @@ -132,7 +132,7 @@ printJsIncludes('..');

-
+ getTwoFactorAuthenticationCaption(); diff --git a/lam/templates/misc/ajax.php b/lam/templates/misc/ajax.php index c88f3fe9..4634518e 100644 --- a/lam/templates/misc/ajax.php +++ b/lam/templates/misc/ajax.php @@ -197,6 +197,7 @@ class Ajax { $isRegistered = isRegistered($userDN); if (!$isRegistered) { $registrationObject = getRegistrationObject($userDN, $isSelfService); + $_SESSION['webauthn_registration'] = $registrationObject; echo json_encode( array( 'action' => 'register', From 2d90e73b2f7bcff483be56102e4ffef64e0a077b Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 30 Nov 2019 08:48:01 +0100 Subject: [PATCH 12/51] webauthn --- lam/composer.json | 3 +- lam/composer.lock | 311 +- lam/lib/2factor.inc | 2 +- .../composer/composer/autoload_files.php | 3 + .../composer/composer/autoload_psr4.php | 5 + .../composer/composer/autoload_static.php | 28 + .../3rdParty/composer/composer/installed.json | 321 +- .../symfony/http-foundation/.gitattributes | 3 + .../symfony/http-foundation/AcceptHeader.php | 165 + .../http-foundation/AcceptHeaderItem.php | 177 + .../http-foundation/BinaryFileResponse.php | 354 ++ .../symfony/http-foundation/CHANGELOG.md | 244 ++ .../symfony/http-foundation/Cookie.php | 302 ++ .../Exception/ConflictingHeadersException.php | 21 + .../Exception/RequestExceptionInterface.php | 21 + .../SuspiciousOperationException.php | 20 + .../ExpressionRequestMatcher.php | 47 + .../File/Exception/AccessDeniedException.php | 25 + .../Exception/CannotWriteFileException.php | 21 + .../File/Exception/ExtensionFileException.php | 21 + .../File/Exception/FileException.php | 21 + .../File/Exception/FileNotFoundException.php | 25 + .../File/Exception/FormSizeFileException.php | 21 + .../File/Exception/IniSizeFileException.php | 21 + .../File/Exception/NoFileException.php | 21 + .../File/Exception/NoTmpDirFileException.php | 21 + .../File/Exception/PartialFileException.php | 21 + .../Exception/UnexpectedTypeException.php | 20 + .../File/Exception/UploadException.php | 21 + .../symfony/http-foundation/File/File.php | 130 + .../symfony/http-foundation/File/Stream.php | 28 + .../http-foundation/File/UploadedFile.php | 281 ++ .../symfony/http-foundation/FileBag.php | 142 + .../symfony/http-foundation/HeaderBag.php | 288 ++ .../symfony/http-foundation/HeaderUtils.php | 224 ++ .../symfony/http-foundation/IpUtils.php | 185 + .../symfony/http-foundation/JsonResponse.php | 215 ++ .../composer/symfony/http-foundation/LICENSE | 19 + .../symfony/http-foundation/ParameterBag.php | 205 ++ .../symfony/http-foundation/README.md | 14 + .../http-foundation/RedirectResponse.php | 105 + .../symfony/http-foundation/Request.php | 2060 +++++++++++ .../http-foundation/RequestMatcher.php | 188 + .../RequestMatcherInterface.php | 27 + .../symfony/http-foundation/RequestStack.php | 103 + .../symfony/http-foundation/Response.php | 1224 +++++++ .../http-foundation/ResponseHeaderBag.php | 293 ++ .../symfony/http-foundation/ServerBag.php | 99 + .../Session/Attribute/AttributeBag.php | 148 + .../Attribute/AttributeBagInterface.php | 61 + .../Attribute/NamespacedAttributeBag.php | 157 + .../Session/Flash/AutoExpireFlashBag.php | 161 + .../Session/Flash/FlashBag.php | 152 + .../Session/Flash/FlashBagInterface.php | 88 + .../http-foundation/Session/Session.php | 268 ++ .../Session/SessionBagInterface.php | 46 + .../Session/SessionBagProxy.php | 83 + .../Session/SessionInterface.php | 166 + .../http-foundation/Session/SessionUtils.php | 59 + .../Handler/AbstractSessionHandler.php | 141 + .../Handler/MemcachedSessionHandler.php | 119 + .../Handler/MigratingSessionHandler.php | 124 + .../Storage/Handler/MongoDbSessionHandler.php | 187 + .../Handler/NativeFileSessionHandler.php | 55 + .../Storage/Handler/NullSessionHandler.php | 76 + .../Storage/Handler/PdoSessionHandler.php | 899 +++++ .../Storage/Handler/RedisSessionHandler.php | 120 + .../Storage/Handler/SessionHandlerFactory.php | 85 + .../Storage/Handler/StrictSessionHandler.php | 103 + .../Session/Storage/MetadataBag.php | 166 + .../Storage/MockArraySessionStorage.php | 252 ++ .../Storage/MockFileSessionStorage.php | 148 + .../Session/Storage/NativeSessionStorage.php | 466 +++ .../Storage/PhpBridgeSessionStorage.php | 64 + .../Session/Storage/Proxy/AbstractProxy.php | 118 + .../Storage/Proxy/SessionHandlerProxy.php | 101 + .../Storage/SessionStorageInterface.php | 131 + .../http-foundation/StreamedResponse.php | 135 + .../Constraint/RequestAttributeValueSame.php | 55 + .../Constraint/ResponseCookieValueSame.php | 85 + .../Test/Constraint/ResponseHasCookie.php | 77 + .../Test/Constraint/ResponseHasHeader.php | 53 + .../Test/Constraint/ResponseHeaderSame.php | 55 + .../Test/Constraint/ResponseIsRedirected.php | 56 + .../Test/Constraint/ResponseIsSuccessful.php | 56 + .../Constraint/ResponseStatusCodeSame.php | 63 + .../symfony/http-foundation/UrlHelper.php | 102 + .../symfony/http-foundation/composer.json | 39 + .../composer/symfony/mime/.gitattributes | 3 + .../composer/symfony/mime/Address.php | 125 + .../symfony/mime/BodyRendererInterface.php | 20 + .../composer/symfony/mime/CHANGELOG.md | 20 + .../composer/symfony/mime/CharacterStream.php | 221 ++ .../composer/symfony/mime/Crypto/SMime.php | 111 + .../symfony/mime/Crypto/SMimeEncrypter.php | 63 + .../symfony/mime/Crypto/SMimeSigner.php | 71 + .../AddMimeTypeGuesserPass.php | 46 + .../3rdParty/composer/symfony/mime/Email.php | 599 ++++ .../mime/Encoder/AddressEncoderInterface.php | 28 + .../mime/Encoder/Base64ContentEncoder.php | 48 + .../symfony/mime/Encoder/Base64Encoder.php | 41 + .../mime/Encoder/Base64MimeHeaderEncoder.php | 43 + .../mime/Encoder/ContentEncoderInterface.php | 30 + .../mime/Encoder/EightBitContentEncoder.php | 35 + .../symfony/mime/Encoder/EncoderInterface.php | 26 + .../mime/Encoder/IdnAddressEncoder.php | 54 + .../Encoder/MimeHeaderEncoderInterface.php | 23 + .../symfony/mime/Encoder/QpContentEncoder.php | 64 + .../symfony/mime/Encoder/QpEncoder.php | 195 + .../mime/Encoder/QpMimeHeaderEncoder.php | 40 + .../symfony/mime/Encoder/Rfc2231Encoder.php | 50 + .../Exception/AddressEncoderException.php | 19 + .../mime/Exception/ExceptionInterface.php | 19 + .../Exception/InvalidArgumentException.php | 19 + .../symfony/mime/Exception/LogicException.php | 19 + .../mime/Exception/RfcComplianceException.php | 19 + .../mime/Exception/RuntimeException.php | 19 + .../mime/FileBinaryMimeTypeGuesser.php | 93 + .../symfony/mime/FileinfoMimeTypeGuesser.php | 63 + .../symfony/mime/Header/AbstractHeader.php | 279 ++ .../symfony/mime/Header/DateHeader.php | 66 + .../symfony/mime/Header/HeaderInterface.php | 65 + .../composer/symfony/mime/Header/Headers.php | 282 ++ .../mime/Header/IdentificationHeader.php | 110 + .../symfony/mime/Header/MailboxHeader.php | 85 + .../symfony/mime/Header/MailboxListHeader.php | 136 + .../mime/Header/ParameterizedHeader.php | 174 + .../symfony/mime/Header/PathHeader.php | 62 + .../mime/Header/UnstructuredHeader.php | 69 + .../3rdParty/composer/symfony/mime/LICENSE | 19 + .../composer/symfony/mime/Message.php | 151 + .../symfony/mime/MessageConverter.php | 125 + .../symfony/mime/MimeTypeGuesserInterface.php | 37 + .../composer/symfony/mime/MimeTypes.php | 3153 +++++++++++++++++ .../symfony/mime/MimeTypesInterface.php | 32 + .../mime/Part/AbstractMultipartPart.php | 99 + .../symfony/mime/Part/AbstractPart.php | 65 + .../composer/symfony/mime/Part/DataPart.php | 161 + .../symfony/mime/Part/MessagePart.php | 62 + .../mime/Part/Multipart/AlternativePart.php | 25 + .../mime/Part/Multipart/DigestPart.php | 31 + .../mime/Part/Multipart/FormDataPart.php | 94 + .../symfony/mime/Part/Multipart/MixedPart.php | 25 + .../mime/Part/Multipart/RelatedPart.php | 55 + .../composer/symfony/mime/Part/SMimePart.php | 116 + .../composer/symfony/mime/Part/TextPart.php | 204 ++ .../3rdParty/composer/symfony/mime/README.md | 13 + .../composer/symfony/mime/RawMessage.php | 88 + .../mime/Resources/bin/update_mime_types.php | 166 + .../Test/Constraint/EmailAddressContains.php | 74 + .../Test/Constraint/EmailAttachmentCount.php | 59 + .../mime/Test/Constraint/EmailHasHeader.php | 57 + .../mime/Test/Constraint/EmailHeaderSame.php | 59 + .../Test/Constraint/EmailHtmlBodyContains.php | 56 + .../Test/Constraint/EmailTextBodyContains.php | 56 + .../composer/symfony/mime/composer.json | 42 + .../symfony/polyfill-ctype/composer.json | 2 +- .../symfony/polyfill-intl-idn/Idn.php | 283 ++ .../symfony/polyfill-intl-idn/LICENSE | 19 + .../symfony/polyfill-intl-idn/README.md | 12 + .../symfony/polyfill-intl-idn/bootstrap.php | 59 + .../symfony/polyfill-intl-idn/composer.json | 36 + .../symfony/polyfill-mbstring/LICENSE | 19 + .../symfony/polyfill-mbstring/Mbstring.php | 840 +++++ .../symfony/polyfill-mbstring/README.md | 13 + .../Resources/unidata/lowerCase.php | 1096 ++++++ .../Resources/unidata/titleCaseRegexp.php | 5 + .../Resources/unidata/upperCase.php | 1104 ++++++ .../symfony/polyfill-mbstring/bootstrap.php | 62 + .../symfony/polyfill-mbstring/composer.json | 34 + .../composer/symfony/polyfill-php72/LICENSE | 19 + .../composer/symfony/polyfill-php72/Php72.php | 216 ++ .../composer/symfony/polyfill-php72/README.md | 27 + .../symfony/polyfill-php72/bootstrap.php | 36 + .../symfony/polyfill-php72/composer.json | 31 + lam/lib/webauthn.inc | 111 +- lam/templates/lib/500_lam.js | 2 +- 177 files changed, 26314 insertions(+), 26 deletions(-) create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/.gitattributes create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/AcceptHeader.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/AcceptHeaderItem.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/BinaryFileResponse.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/CHANGELOG.md create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Cookie.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Exception/ConflictingHeadersException.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Exception/RequestExceptionInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Exception/SuspiciousOperationException.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/ExpressionRequestMatcher.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/AccessDeniedException.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/CannotWriteFileException.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/ExtensionFileException.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FileException.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FileNotFoundException.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FormSizeFileException.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/IniSizeFileException.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/NoFileException.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/NoTmpDirFileException.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/PartialFileException.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/UnexpectedTypeException.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/UploadException.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/File/File.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/File/Stream.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/File/UploadedFile.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/FileBag.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/HeaderBag.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/HeaderUtils.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/IpUtils.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/JsonResponse.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/LICENSE create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/ParameterBag.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/README.md create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/RedirectResponse.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Request.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/RequestMatcher.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/RequestMatcherInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/RequestStack.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Response.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/ResponseHeaderBag.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/ServerBag.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/AttributeBag.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/FlashBag.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/FlashBagInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Session.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionBagInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionBagProxy.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionUtils.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MetadataBag.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/NativeSessionStorage.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/SessionStorageInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/StreamedResponse.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/UrlHelper.php create mode 100644 lam/lib/3rdParty/composer/symfony/http-foundation/composer.json create mode 100644 lam/lib/3rdParty/composer/symfony/mime/.gitattributes create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Address.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/BodyRendererInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/CHANGELOG.md create mode 100644 lam/lib/3rdParty/composer/symfony/mime/CharacterStream.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Crypto/SMime.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Crypto/SMimeEncrypter.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Crypto/SMimeSigner.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Email.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Encoder/AddressEncoderInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Encoder/Base64ContentEncoder.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Encoder/Base64Encoder.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Encoder/Base64MimeHeaderEncoder.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Encoder/ContentEncoderInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Encoder/EightBitContentEncoder.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Encoder/EncoderInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Encoder/IdnAddressEncoder.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Encoder/MimeHeaderEncoderInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Encoder/QpContentEncoder.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Encoder/QpEncoder.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Encoder/QpMimeHeaderEncoder.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Encoder/Rfc2231Encoder.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Exception/AddressEncoderException.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Exception/ExceptionInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Exception/InvalidArgumentException.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Exception/LogicException.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Exception/RfcComplianceException.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Exception/RuntimeException.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/FileBinaryMimeTypeGuesser.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/FileinfoMimeTypeGuesser.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Header/AbstractHeader.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Header/DateHeader.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Header/HeaderInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Header/Headers.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Header/IdentificationHeader.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Header/MailboxHeader.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Header/MailboxListHeader.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Header/ParameterizedHeader.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Header/PathHeader.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Header/UnstructuredHeader.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/LICENSE create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Message.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/MessageConverter.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/MimeTypeGuesserInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/MimeTypes.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/MimeTypesInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Part/AbstractMultipartPart.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Part/AbstractPart.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Part/DataPart.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Part/MessagePart.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/AlternativePart.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/DigestPart.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/FormDataPart.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/MixedPart.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/RelatedPart.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Part/SMimePart.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Part/TextPart.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/README.md create mode 100644 lam/lib/3rdParty/composer/symfony/mime/RawMessage.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Resources/bin/update_mime_types.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailAddressContains.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailAttachmentCount.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailHasHeader.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailHeaderSame.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailTextBodyContains.php create mode 100644 lam/lib/3rdParty/composer/symfony/mime/composer.json create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/Idn.php create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/LICENSE create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/README.md create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/bootstrap.php create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/composer.json create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-mbstring/LICENSE create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-mbstring/Mbstring.php create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-mbstring/README.md create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-mbstring/Resources/unidata/upperCase.php create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-mbstring/bootstrap.php create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-mbstring/composer.json create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-php72/LICENSE create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-php72/Php72.php create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-php72/README.md create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-php72/bootstrap.php create mode 100644 lam/lib/3rdParty/composer/symfony/polyfill-php72/composer.json diff --git a/lam/composer.json b/lam/composer.json index a78d3bba..7eadeac4 100644 --- a/lam/composer.json +++ b/lam/composer.json @@ -3,6 +3,7 @@ "vendor-dir": "lib/3rdParty/composer" }, "require" : { - "web-auth/webauthn-lib" : "2.1.7" + "web-auth/webauthn-lib" : "2.1.7", + "symfony/http-foundation" : "5.0.0" } } \ No newline at end of file diff --git a/lam/composer.lock b/lam/composer.lock index c7cd584c..b60ea17c 100644 --- a/lam/composer.lock +++ b/lam/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "df09efa5a29ab9799e4e0c0ff066bfef", + "content-hash": "ba2488b4997bb9328901b5be3ed4bed0", "packages": [ { "name": "beberlei/assert", @@ -646,17 +646,134 @@ "time": "2019-08-15T14:53:55+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.12.0", + "name": "symfony/http-foundation", + "version": "v5.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + "url": "https://github.com/symfony/http-foundation.git", + "reference": "c5c226b6f164ae4f95c4bffbe940c81050940eda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/c5c226b6f164ae4f95c4bffbe940c81050940eda", + "reference": "c5c226b6f164ae4f95c4bffbe940c81050940eda", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "symfony/mime": "^4.4|^5.0", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/expression-language": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2019-11-18T17:27:11+00:00" + }, + { + "name": "symfony/mime", + "version": "v5.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "76f3c09b7382bf979af7bcd8e6f8033f1324285e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/76f3c09b7382bf979af7bcd8e6f8033f1324285e", + "reference": "76f3c09b7382bf979af7bcd8e6f8033f1324285e", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/mailer": "<4.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10", + "symfony/dependency-injection": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A library to manipulate MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "time": "2019-11-18T17:27:11+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.13.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", "shasum": "" }, "require": { @@ -668,7 +785,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -701,7 +818,183 @@ "polyfill", "portable" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-11-27T13:56:44+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.13.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6f9c239e61e1b0c9229a28ff89a812dc449c3d46", + "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php72": "^1.9" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "time": "2019-11-27T13:56:44+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.13.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f", + "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2019-11-27T14:18:11+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.13.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/66fea50f6cb37a35eea048d75a7d99a45b586038", + "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-11-27T13:56:44+00:00" }, { "name": "web-auth/cose-lib", diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index 72862e11..b50c8c31 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -542,7 +542,7 @@ class WebauthnProvider extends BaseProvider { */ public function verify2ndFactor($user, $password, $serial, $twoFactorInput) { logNewMessage(LOG_DEBUG, 'WebauthnProvider: Checking 2nd factor for ' . $user); - $response = $_POST['sig_response']; + $response = base64_decode($_POST['sig_response']); include_once __DIR__ . '/3rdParty/composer/autoload.php'; include_once __DIR__ . '/webauthn.inc'; $registrationObject = $_SESSION['webauthn_registration']; diff --git a/lam/lib/3rdParty/composer/composer/autoload_files.php b/lam/lib/3rdParty/composer/composer/autoload_files.php index 5f9ad9bc..d58d66de 100644 --- a/lam/lib/3rdParty/composer/composer/autoload_files.php +++ b/lam/lib/3rdParty/composer/composer/autoload_files.php @@ -6,6 +6,9 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname(dirname(dirname($vendorDir))); return array( + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', 'a4ecaeafb8cfb009ad0e052c90355e98' => $vendorDir . '/beberlei/assert/lib/Assert/functions.php', + '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', ); diff --git a/lam/lib/3rdParty/composer/composer/autoload_psr4.php b/lam/lib/3rdParty/composer/composer/autoload_psr4.php index aca8a877..5d88d7da 100644 --- a/lam/lib/3rdParty/composer/composer/autoload_psr4.php +++ b/lam/lib/3rdParty/composer/composer/autoload_psr4.php @@ -8,7 +8,12 @@ $baseDir = dirname(dirname(dirname($vendorDir))); return array( 'Webauthn\\MetadataService\\' => array($vendorDir . '/web-auth/metadata-service/src'), 'Webauthn\\' => array($vendorDir . '/web-auth/webauthn-lib/src'), + 'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'), 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Component\\Mime\\' => array($vendorDir . '/symfony/mime'), + 'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'), 'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'), 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), diff --git a/lam/lib/3rdParty/composer/composer/autoload_static.php b/lam/lib/3rdParty/composer/composer/autoload_static.php index 5659a0d0..ab4e11ea 100644 --- a/lam/lib/3rdParty/composer/composer/autoload_static.php +++ b/lam/lib/3rdParty/composer/composer/autoload_static.php @@ -7,8 +7,11 @@ namespace Composer\Autoload; class ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5 { public static $files = array ( + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', 'a4ecaeafb8cfb009ad0e052c90355e98' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/functions.php', + '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + 'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', ); public static $prefixLengthsPsr4 = array ( @@ -19,7 +22,12 @@ class ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5 ), 'S' => array ( + 'Symfony\\Polyfill\\Php72\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Intl\\Idn\\' => 26, 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Component\\Mime\\' => 23, + 'Symfony\\Component\\HttpFoundation\\' => 33, ), 'R' => array ( @@ -66,10 +74,30 @@ class ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5 array ( 0 => __DIR__ . '/..' . '/web-auth/webauthn-lib/src', ), + 'Symfony\\Polyfill\\Php72\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php72', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Intl\\Idn\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn', + ), 'Symfony\\Polyfill\\Ctype\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', ), + 'Symfony\\Component\\Mime\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/mime', + ), + 'Symfony\\Component\\HttpFoundation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-foundation', + ), 'Ramsey\\Uuid\\' => array ( 0 => __DIR__ . '/..' . '/ramsey/uuid/src', diff --git a/lam/lib/3rdParty/composer/composer/installed.json b/lam/lib/3rdParty/composer/composer/installed.json index 40511296..aada8d20 100644 --- a/lam/lib/3rdParty/composer/composer/installed.json +++ b/lam/lib/3rdParty/composer/composer/installed.json @@ -661,18 +661,139 @@ ] }, { - "name": "symfony/polyfill-ctype", - "version": "v1.12.0", - "version_normalized": "1.12.0.0", + "name": "symfony/http-foundation", + "version": "v5.0.0", + "version_normalized": "5.0.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + "url": "https://github.com/symfony/http-foundation.git", + "reference": "c5c226b6f164ae4f95c4bffbe940c81050940eda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/c5c226b6f164ae4f95c4bffbe940c81050940eda", + "reference": "c5c226b6f164ae4f95c4bffbe940c81050940eda", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "symfony/mime": "^4.4|^5.0", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/expression-language": "^4.4|^5.0" + }, + "time": "2019-11-18T17:27:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/mime", + "version": "v5.0.0", + "version_normalized": "5.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "76f3c09b7382bf979af7bcd8e6f8033f1324285e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/76f3c09b7382bf979af7bcd8e6f8033f1324285e", + "reference": "76f3c09b7382bf979af7bcd8e6f8033f1324285e", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/mailer": "<4.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10", + "symfony/dependency-injection": "^4.4|^5.0" + }, + "time": "2019-11-18T17:27:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A library to manipulate MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ] + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.13.0", + "version_normalized": "1.13.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", "shasum": "" }, "require": { @@ -681,11 +802,11 @@ "suggest": { "ext-ctype": "For best performance" }, - "time": "2019-08-06T08:03:45+00:00", + "time": "2019-11-27T13:56:44+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.13-dev" } }, "installation-source": "dist", @@ -720,6 +841,188 @@ "portable" ] }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.13.0", + "version_normalized": "1.13.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6f9c239e61e1b0c9229a28ff89a812dc449c3d46", + "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php72": "^1.9" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2019-11-27T13:56:44+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.13.0", + "version_normalized": "1.13.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f", + "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2019-11-27T14:18:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.13.0", + "version_normalized": "1.13.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/66fea50f6cb37a35eea048d75a7d99a45b586038", + "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2019-11-27T13:56:44+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ] + }, { "name": "web-auth/cose-lib", "version": "v2.1.7", diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/.gitattributes b/lam/lib/3rdParty/composer/symfony/http-foundation/.gitattributes new file mode 100644 index 00000000..ebb92870 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/.gitattributes @@ -0,0 +1,3 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitignore export-ignore diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/AcceptHeader.php b/lam/lib/3rdParty/composer/symfony/http-foundation/AcceptHeader.php new file mode 100644 index 00000000..90f9f479 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/AcceptHeader.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents an Accept-* header. + * + * An accept header is compound with a list of items, + * sorted by descending quality. + * + * @author Jean-François Simon + */ +class AcceptHeader +{ + /** + * @var AcceptHeaderItem[] + */ + private $items = []; + + /** + * @var bool + */ + private $sorted = true; + + /** + * @param AcceptHeaderItem[] $items + */ + public function __construct(array $items) + { + foreach ($items as $item) { + $this->add($item); + } + } + + /** + * Builds an AcceptHeader instance from a string. + * + * @return self + */ + public static function fromString(?string $headerValue) + { + $index = 0; + + $parts = HeaderUtils::split($headerValue ?? '', ',;='); + + return new self(array_map(function ($subParts) use (&$index) { + $part = array_shift($subParts); + $attributes = HeaderUtils::combine($subParts); + + $item = new AcceptHeaderItem($part[0], $attributes); + $item->setIndex($index++); + + return $item; + }, $parts)); + } + + /** + * Returns header value's string representation. + * + * @return string + */ + public function __toString() + { + return implode(',', $this->items); + } + + /** + * Tests if header has given value. + * + * @return bool + */ + public function has(string $value) + { + return isset($this->items[$value]); + } + + /** + * Returns given value's item, if exists. + * + * @return AcceptHeaderItem|null + */ + public function get(string $value) + { + return $this->items[$value] ?? $this->items[explode('/', $value)[0].'/*'] ?? $this->items['*/*'] ?? $this->items['*'] ?? null; + } + + /** + * Adds an item. + * + * @return $this + */ + public function add(AcceptHeaderItem $item) + { + $this->items[$item->getValue()] = $item; + $this->sorted = false; + + return $this; + } + + /** + * Returns all items. + * + * @return AcceptHeaderItem[] + */ + public function all() + { + $this->sort(); + + return $this->items; + } + + /** + * Filters items on their value using given regex. + * + * @return self + */ + public function filter(string $pattern) + { + return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) { + return preg_match($pattern, $item->getValue()); + })); + } + + /** + * Returns first item. + * + * @return AcceptHeaderItem|null + */ + public function first() + { + $this->sort(); + + return !empty($this->items) ? reset($this->items) : null; + } + + /** + * Sorts items by descending quality. + */ + private function sort(): void + { + if (!$this->sorted) { + uasort($this->items, function (AcceptHeaderItem $a, AcceptHeaderItem $b) { + $qA = $a->getQuality(); + $qB = $b->getQuality(); + + if ($qA === $qB) { + return $a->getIndex() > $b->getIndex() ? 1 : -1; + } + + return $qA > $qB ? -1 : 1; + }); + + $this->sorted = true; + } + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/AcceptHeaderItem.php b/lam/lib/3rdParty/composer/symfony/http-foundation/AcceptHeaderItem.php new file mode 100644 index 00000000..bc4014e5 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/AcceptHeaderItem.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents an Accept-* header item. + * + * @author Jean-François Simon + */ +class AcceptHeaderItem +{ + private $value; + private $quality = 1.0; + private $index = 0; + private $attributes = []; + + public function __construct(string $value, array $attributes = []) + { + $this->value = $value; + foreach ($attributes as $name => $value) { + $this->setAttribute($name, $value); + } + } + + /** + * Builds an AcceptHeaderInstance instance from a string. + * + * @return self + */ + public static function fromString(?string $itemValue) + { + $parts = HeaderUtils::split($itemValue ?? '', ';='); + + $part = array_shift($parts); + $attributes = HeaderUtils::combine($parts); + + return new self($part[0], $attributes); + } + + /** + * Returns header value's string representation. + * + * @return string + */ + public function __toString() + { + $string = $this->value.($this->quality < 1 ? ';q='.$this->quality : ''); + if (\count($this->attributes) > 0) { + $string .= '; '.HeaderUtils::toString($this->attributes, ';'); + } + + return $string; + } + + /** + * Set the item value. + * + * @return $this + */ + public function setValue(string $value) + { + $this->value = $value; + + return $this; + } + + /** + * Returns the item value. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Set the item quality. + * + * @return $this + */ + public function setQuality(float $quality) + { + $this->quality = $quality; + + return $this; + } + + /** + * Returns the item quality. + * + * @return float + */ + public function getQuality() + { + return $this->quality; + } + + /** + * Set the item index. + * + * @return $this + */ + public function setIndex(int $index) + { + $this->index = $index; + + return $this; + } + + /** + * Returns the item index. + * + * @return int + */ + public function getIndex() + { + return $this->index; + } + + /** + * Tests if an attribute exists. + * + * @return bool + */ + public function hasAttribute(string $name) + { + return isset($this->attributes[$name]); + } + + /** + * Returns an attribute by its name. + * + * @param mixed $default + * + * @return mixed + */ + public function getAttribute(string $name, $default = null) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + } + + /** + * Returns all attributes. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Set an attribute. + * + * @return $this + */ + public function setAttribute(string $name, string $value) + { + if ('q' === $name) { + $this->quality = (float) $value; + } else { + $this->attributes[$name] = $value; + } + + return $this; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/BinaryFileResponse.php b/lam/lib/3rdParty/composer/symfony/http-foundation/BinaryFileResponse.php new file mode 100644 index 00000000..ad839cef --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/BinaryFileResponse.php @@ -0,0 +1,354 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\File; + +/** + * BinaryFileResponse represents an HTTP response delivering a file. + * + * @author Niklas Fiekas + * @author stealth35 + * @author Igor Wiedler + * @author Jordan Alliot + * @author Sergey Linnik + */ +class BinaryFileResponse extends Response +{ + protected static $trustXSendfileTypeHeader = false; + + /** + * @var File + */ + protected $file; + protected $offset = 0; + protected $maxlen = -1; + protected $deleteFileAfterSend = false; + + /** + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $public Files are public by default + * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename + * @param bool $autoEtag Whether the ETag header should be automatically set + * @param bool $autoLastModified Whether the Last-Modified header should be automatically set + */ + public function __construct($file, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) + { + parent::__construct(null, $status, $headers); + + $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified); + + if ($public) { + $this->setPublic(); + } + } + + /** + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $public Files are public by default + * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename + * @param bool $autoEtag Whether the ETag header should be automatically set + * @param bool $autoLastModified Whether the Last-Modified header should be automatically set + * + * @return static + */ + public static function create($file = null, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) + { + return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified); + } + + /** + * Sets the file to stream. + * + * @param \SplFileInfo|string $file The file to stream + * + * @return $this + * + * @throws FileException + */ + public function setFile($file, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) + { + if (!$file instanceof File) { + if ($file instanceof \SplFileInfo) { + $file = new File($file->getPathname()); + } else { + $file = new File((string) $file); + } + } + + if (!$file->isReadable()) { + throw new FileException('File must be readable.'); + } + + $this->file = $file; + + if ($autoEtag) { + $this->setAutoEtag(); + } + + if ($autoLastModified) { + $this->setAutoLastModified(); + } + + if ($contentDisposition) { + $this->setContentDisposition($contentDisposition); + } + + return $this; + } + + /** + * Gets the file. + * + * @return File The file to stream + */ + public function getFile() + { + return $this->file; + } + + /** + * Automatically sets the Last-Modified header according the file modification date. + */ + public function setAutoLastModified() + { + $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime())); + + return $this; + } + + /** + * Automatically sets the ETag header according to the checksum of the file. + */ + public function setAutoEtag() + { + $this->setEtag(base64_encode(hash_file('sha256', $this->file->getPathname(), true))); + + return $this; + } + + /** + * Sets the Content-Disposition header with the given filename. + * + * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT + * @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file + * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename + * + * @return $this + */ + public function setContentDisposition(string $disposition, string $filename = '', string $filenameFallback = '') + { + if ('' === $filename) { + $filename = $this->file->getFilename(); + } + + if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) { + $encoding = mb_detect_encoding($filename, null, true) ?: '8bit'; + + for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) { + $char = mb_substr($filename, $i, 1, $encoding); + + if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) { + $filenameFallback .= '_'; + } else { + $filenameFallback .= $char; + } + } + } + + $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback); + $this->headers->set('Content-Disposition', $dispositionHeader); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function prepare(Request $request) + { + if (!$this->headers->has('Content-Type')) { + $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream'); + } + + if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + $this->ensureIEOverSSLCompatibility($request); + + $this->offset = 0; + $this->maxlen = -1; + + if (false === $fileSize = $this->file->getSize()) { + return $this; + } + $this->headers->set('Content-Length', $fileSize); + + if (!$this->headers->has('Accept-Ranges')) { + // Only accept ranges on safe HTTP methods + $this->headers->set('Accept-Ranges', $request->isMethodSafe() ? 'bytes' : 'none'); + } + + if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) { + // Use X-Sendfile, do not send any content. + $type = $request->headers->get('X-Sendfile-Type'); + $path = $this->file->getRealPath(); + // Fall back to scheme://path for stream wrapped locations. + if (false === $path) { + $path = $this->file->getPathname(); + } + if ('x-accel-redirect' === strtolower($type)) { + // Do X-Accel-Mapping substitutions. + // @link http://wiki.nginx.org/X-accel#X-Accel-Redirect + $parts = HeaderUtils::split($request->headers->get('X-Accel-Mapping', ''), ',='); + foreach ($parts as $part) { + list($pathPrefix, $location) = $part; + if (substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) { + $path = $location.substr($path, \strlen($pathPrefix)); + // Only set X-Accel-Redirect header if a valid URI can be produced + // as nginx does not serve arbitrary file paths. + $this->headers->set($type, $path); + $this->maxlen = 0; + break; + } + } + } else { + $this->headers->set($type, $path); + $this->maxlen = 0; + } + } elseif ($request->headers->has('Range')) { + // Process the range headers. + if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) { + $range = $request->headers->get('Range'); + + list($start, $end) = explode('-', substr($range, 6), 2) + [0]; + + $end = ('' === $end) ? $fileSize - 1 : (int) $end; + + if ('' === $start) { + $start = $fileSize - $end; + $end = $fileSize - 1; + } else { + $start = (int) $start; + } + + if ($start <= $end) { + if ($start < 0 || $end > $fileSize - 1) { + $this->setStatusCode(416); + $this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize)); + } elseif (0 !== $start || $end !== $fileSize - 1) { + $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1; + $this->offset = $start; + + $this->setStatusCode(206); + $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize)); + $this->headers->set('Content-Length', $end - $start + 1); + } + } + } + } + + return $this; + } + + private function hasValidIfRangeHeader(?string $header): bool + { + if ($this->getEtag() === $header) { + return true; + } + + if (null === $lastModified = $this->getLastModified()) { + return false; + } + + return $lastModified->format('D, d M Y H:i:s').' GMT' === $header; + } + + /** + * Sends the file. + * + * {@inheritdoc} + */ + public function sendContent() + { + if (!$this->isSuccessful()) { + return parent::sendContent(); + } + + if (0 === $this->maxlen) { + return $this; + } + + $out = fopen('php://output', 'wb'); + $file = fopen($this->file->getPathname(), 'rb'); + + stream_copy_to_stream($file, $out, $this->maxlen, $this->offset); + + fclose($out); + fclose($file); + + if ($this->deleteFileAfterSend && file_exists($this->file->getPathname())) { + unlink($this->file->getPathname()); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @throws \LogicException when the content is not null + */ + public function setContent(?string $content) + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getContent() + { + return false; + } + + /** + * Trust X-Sendfile-Type header. + */ + public static function trustXSendfileTypeHeader() + { + self::$trustXSendfileTypeHeader = true; + } + + /** + * If this is set to true, the file will be unlinked after the request is send + * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used. + * + * @return $this + */ + public function deleteFileAfterSend(bool $shouldDelete = true) + { + $this->deleteFileAfterSend = $shouldDelete; + + return $this; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/CHANGELOG.md b/lam/lib/3rdParty/composer/symfony/http-foundation/CHANGELOG.md new file mode 100644 index 00000000..bab37d1b --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/CHANGELOG.md @@ -0,0 +1,244 @@ +CHANGELOG +========= + +5.0.0 +----- + + * made `Cookie` auto-secure and lax by default + * removed classes in the `MimeType` namespace, use the Symfony Mime component instead + * removed method `UploadedFile::getClientSize()` and the related constructor argument + * made `Request::getSession()` throw if the session has not been set before + * removed `Response::HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL` + * passing a null url when instantiating a `RedirectResponse` is not allowed + +4.4.0 +----- + + * passing arguments to `Request::isMethodSafe()` is deprecated. + * `ApacheRequest` is deprecated, use the `Request` class instead. + * passing a third argument to `HeaderBag::get()` is deprecated, use method `all()` instead + * `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column, + make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database + to speed up garbage collection of expired sessions. + * added `SessionHandlerFactory` to create session handlers with a DSN + * added `IpUtils::anonymize()` to help with GDPR compliance. + +4.3.0 +----- + + * added PHPUnit constraints: `RequestAttributeValueSame`, `ResponseCookieValueSame`, `ResponseHasCookie`, + `ResponseHasHeader`, `ResponseHeaderSame`, `ResponseIsRedirected`, `ResponseIsSuccessful`, and `ResponseStatusCodeSame` + * deprecated `MimeTypeGuesserInterface` and `ExtensionGuesserInterface` in favor of `Symfony\Component\Mime\MimeTypesInterface`. + * deprecated `MimeType` and `MimeTypeExtensionGuesser` in favor of `Symfony\Component\Mime\MimeTypes`. + * deprecated `FileBinaryMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileBinaryMimeTypeGuesser`. + * deprecated `FileinfoMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileinfoMimeTypeGuesser`. + * added `UrlHelper` that allows to get an absolute URL and a relative path for a given path + +4.2.0 +----- + + * the default value of the "$secure" and "$samesite" arguments of Cookie's constructor + will respectively change from "false" to "null" and from "null" to "lax" in Symfony + 5.0, you should define their values explicitly or use "Cookie::create()" instead. + * added `matchPort()` in RequestMatcher + +4.1.3 +----- + + * [BC BREAK] Support for the IIS-only `X_ORIGINAL_URL` and `X_REWRITE_URL` + HTTP headers has been dropped for security reasons. + +4.1.0 +----- + + * Query string normalization uses `parse_str()` instead of custom parsing logic. + * Passing the file size to the constructor of the `UploadedFile` class is deprecated. + * The `getClientSize()` method of the `UploadedFile` class is deprecated. Use `getSize()` instead. + * added `RedisSessionHandler` to use Redis as a session storage + * The `get()` method of the `AcceptHeader` class now takes into account the + `*` and `*/*` default values (if they are present in the Accept HTTP header) + when looking for items. + * deprecated `Request::getSession()` when no session has been set. Use `Request::hasSession()` instead. + * added `CannotWriteFileException`, `ExtensionFileException`, `FormSizeFileException`, + `IniSizeFileException`, `NoFileException`, `NoTmpDirFileException`, `PartialFileException` to + handle failed `UploadedFile`. + * added `MigratingSessionHandler` for migrating between two session handlers without losing sessions + * added `HeaderUtils`. + +4.0.0 +----- + + * the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` + methods have been removed + * the `Request::HEADER_CLIENT_IP` constant has been removed, use + `Request::HEADER_X_FORWARDED_FOR` instead + * the `Request::HEADER_CLIENT_HOST` constant has been removed, use + `Request::HEADER_X_FORWARDED_HOST` instead + * the `Request::HEADER_CLIENT_PROTO` constant has been removed, use + `Request::HEADER_X_FORWARDED_PROTO` instead + * the `Request::HEADER_CLIENT_PORT` constant has been removed, use + `Request::HEADER_X_FORWARDED_PORT` instead + * checking for cacheable HTTP methods using the `Request::isMethodSafe()` + method (by not passing `false` as its argument) is not supported anymore and + throws a `\BadMethodCallException` + * the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes have been removed + * setting session save handlers that do not implement `\SessionHandlerInterface` in + `NativeSessionStorage::setSaveHandler()` is not supported anymore and throws a + `\TypeError` + +3.4.0 +----- + + * implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new + `AbstractSessionHandler` base class and a new `StrictSessionHandler` wrapper + * deprecated the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes + * deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()` + * deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead + * deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead + +3.3.0 +----- + + * the `Request::setTrustedProxies()` method takes a new `$trustedHeaderSet` argument, + see https://symfony.com/doc/current/deployment/proxies.html for more info, + * deprecated the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` methods, + * added `File\Stream`, to be passed to `BinaryFileResponse` when the size of the served file is unknown, + disabling `Range` and `Content-Length` handling, switching to chunked encoding instead + * added the `Cookie::fromString()` method that allows to create a cookie from a + raw header string + +3.1.0 +----- + + * Added support for creating `JsonResponse` with a string of JSON data + +3.0.0 +----- + + * The precedence of parameters returned from `Request::get()` changed from "GET, PATH, BODY" to "PATH, GET, BODY" + +2.8.0 +----- + + * Finding deep items in `ParameterBag::get()` is deprecated since version 2.8 and + will be removed in 3.0. + +2.6.0 +----- + + * PdoSessionHandler changes + - implemented different session locking strategies to prevent loss of data by concurrent access to the same session + - [BC BREAK] save session data in a binary column without base64_encode + - [BC BREAK] added lifetime column to the session table which allows to have different lifetimes for each session + - implemented lazy connections that are only opened when a session is used by either passing a dsn string + explicitly or falling back to session.save_path ini setting + - added a createTable method that initializes a correctly defined table depending on the database vendor + +2.5.0 +----- + + * added `JsonResponse::setEncodingOptions()` & `JsonResponse::getEncodingOptions()` for easier manipulation + of the options used while encoding data to JSON format. + +2.4.0 +----- + + * added RequestStack + * added Request::getEncodings() + * added accessors methods to session handlers + +2.3.0 +----- + + * added support for ranges of IPs in trusted proxies + * `UploadedFile::isValid` now returns false if the file was not uploaded via HTTP (in a non-test mode) + * Improved error-handling of `\Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler` + to ensure the supplied PDO handler throws Exceptions on error (as the class expects). Added related test cases + to verify that Exceptions are properly thrown when the PDO queries fail. + +2.2.0 +----- + + * fixed the Request::create() precedence (URI information always take precedence now) + * added Request::getTrustedProxies() + * deprecated Request::isProxyTrusted() + * [BC BREAK] JsonResponse does not turn a top level empty array to an object anymore, use an ArrayObject to enforce objects + * added a IpUtils class to check if an IP belongs to a CIDR + * added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method) + * disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to + enable it, and Request::getHttpMethodParameterOverride() to check if it is supported) + * Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3 + * Deprecated Flashbag::count() and \Countable interface, will be removed in 2.3 + +2.1.0 +----- + + * added Request::getSchemeAndHttpHost() and Request::getUserInfo() + * added a fluent interface to the Response class + * added Request::isProxyTrusted() + * added JsonResponse + * added a getTargetUrl method to RedirectResponse + * added support for streamed responses + * made Response::prepare() method the place to enforce HTTP specification + * [BC BREAK] moved management of the locale from the Session class to the Request class + * added a generic access to the PHP built-in filter mechanism: ParameterBag::filter() + * made FileBinaryMimeTypeGuesser command configurable + * added Request::getUser() and Request::getPassword() + * added support for the PATCH method in Request + * removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3 + * added ResponseHeaderBag::makeDisposition() (implements RFC 6266) + * made mimetype to extension conversion configurable + * [BC BREAK] Moved all session related classes and interfaces into own namespace, as + `Symfony\Component\HttpFoundation\Session` and renamed classes accordingly. + Session handlers are located in the subnamespace `Symfony\Component\HttpFoundation\Session\Handler`. + * SessionHandlers must implement `\SessionHandlerInterface` or extend from the + `Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class. + * Added internal storage driver proxy mechanism for forward compatibility with + PHP 5.4 `\SessionHandler` class. + * Added session handlers for custom Memcache, Memcached and Null session save handlers. + * [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`. + * [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and + `remove()`. Added `getBag()`, `registerBag()`. The `NativeSessionStorage` class + is a mediator for the session storage internals including the session handlers + which do the real work of participating in the internal PHP session workflow. + * [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit + and functional testing without starting real PHP sessions. Removed + `ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit + tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage` + for functional tests. These do not interact with global session ini + configuration values, session functions or `$_SESSION` superglobal. This means + they can be configured directly allowing multiple instances to work without + conflicting in the same PHP process. + * [BC BREAK] Removed the `close()` method from the `Session` class, as this is + now redundant. + * Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()` + `getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag()` instead + which returns a `FlashBagInterface`. + * `Session->clear()` now only clears session attributes as before it cleared + flash messages and attributes. `Session->getFlashBag()->all()` clears flashes now. + * Session data is now managed by `SessionBagInterface` to better encapsulate + session data. + * Refactored session attribute and flash messages system to their own + `SessionBagInterface` implementations. + * Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This + implementation is ESI compatible. + * Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire + behavior of messages auto expiring after one page page load. Messages must + be retrieved by `get()` or `all()`. + * Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate + attributes storage behavior from 2.0.x (default). + * Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for + namespace session attributes. + * Flash API can stores messages in an array so there may be multiple messages + per flash type. The old `Session` class API remains without BC break as it + will allow single messages as before. + * Added basic session meta-data to the session to record session create time, + last updated time, and the lifetime of the session cookie that was provided + to the client. + * Request::getClientIp() method doesn't take a parameter anymore but bases + itself on the trustProxy parameter. + * Added isMethod() to Request object. + * [BC BREAK] The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of + a `Request` now all return a raw value (vs a urldecoded value before). Any call + to one of these methods must be checked and wrapped in a `rawurldecode()` if + needed. diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Cookie.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Cookie.php new file mode 100644 index 00000000..fc711ee6 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Cookie.php @@ -0,0 +1,302 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents a cookie. + * + * @author Johannes M. Schmitt + */ +class Cookie +{ + const SAMESITE_NONE = 'none'; + const SAMESITE_LAX = 'lax'; + const SAMESITE_STRICT = 'strict'; + + protected $name; + protected $value; + protected $domain; + protected $expire; + protected $path; + protected $secure; + protected $httpOnly; + + private $raw; + private $sameSite; + private $secureDefault = false; + + private static $reservedCharsList = "=,; \t\r\n\v\f"; + private static $reservedCharsFrom = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"]; + private static $reservedCharsTo = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C']; + + /** + * Creates cookie from raw header string. + * + * @return static + */ + public static function fromString(string $cookie, bool $decode = false) + { + $data = [ + 'expires' => 0, + 'path' => '/', + 'domain' => null, + 'secure' => false, + 'httponly' => false, + 'raw' => !$decode, + 'samesite' => null, + ]; + + $parts = HeaderUtils::split($cookie, ';='); + $part = array_shift($parts); + + $name = $decode ? urldecode($part[0]) : $part[0]; + $value = isset($part[1]) ? ($decode ? urldecode($part[1]) : $part[1]) : null; + + $data = HeaderUtils::combine($parts) + $data; + + if (isset($data['max-age'])) { + $data['expires'] = time() + (int) $data['max-age']; + } + + return new static($name, $value, $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']); + } + + public static function create(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX): self + { + return new self($name, $value, $expire, $path, $domain, $secure, $httpOnly, $raw, $sameSite); + } + + /** + * @param string $name The name of the cookie + * @param string|null $value The value of the cookie + * @param int|string|\DateTimeInterface $expire The time the cookie expires + * @param string $path The path on the server in which the cookie will be available on + * @param string|null $domain The domain that the cookie is available to + * @param bool|null $secure Whether the client should send back the cookie only over HTTPS or null to auto-enable this when the request is already using HTTPS + * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol + * @param bool $raw Whether the cookie value should be sent with no url encoding + * @param string|null $sameSite Whether the cookie will be available for cross-site requests + * + * @throws \InvalidArgumentException + */ + public function __construct(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = 'lax') + { + // from PHP source code + if ($raw && false !== strpbrk($name, self::$reservedCharsList)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); + } + + if (empty($name)) { + throw new \InvalidArgumentException('The cookie name cannot be empty.'); + } + + // convert expiration time to a Unix timestamp + if ($expire instanceof \DateTimeInterface) { + $expire = $expire->format('U'); + } elseif (!is_numeric($expire)) { + $expire = strtotime($expire); + + if (false === $expire) { + throw new \InvalidArgumentException('The cookie expiration time is not valid.'); + } + } + + $this->name = $name; + $this->value = $value; + $this->domain = $domain; + $this->expire = 0 < $expire ? (int) $expire : 0; + $this->path = empty($path) ? '/' : $path; + $this->secure = $secure; + $this->httpOnly = $httpOnly; + $this->raw = $raw; + + if ('' === $sameSite) { + $sameSite = null; + } elseif (null !== $sameSite) { + $sameSite = strtolower($sameSite); + } + + if (!\in_array($sameSite, [self::SAMESITE_LAX, self::SAMESITE_STRICT, self::SAMESITE_NONE, null], true)) { + throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.'); + } + + $this->sameSite = $sameSite; + } + + /** + * Returns the cookie as a string. + * + * @return string The cookie + */ + public function __toString() + { + if ($this->isRaw()) { + $str = $this->getName(); + } else { + $str = str_replace(self::$reservedCharsFrom, self::$reservedCharsTo, $this->getName()); + } + + $str .= '='; + + if ('' === (string) $this->getValue()) { + $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0'; + } else { + $str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue()); + + if (0 !== $this->getExpiresTime()) { + $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; Max-Age='.$this->getMaxAge(); + } + } + + if ($this->getPath()) { + $str .= '; path='.$this->getPath(); + } + + if ($this->getDomain()) { + $str .= '; domain='.$this->getDomain(); + } + + if (true === $this->isSecure()) { + $str .= '; secure'; + } + + if (true === $this->isHttpOnly()) { + $str .= '; httponly'; + } + + if (null !== $this->getSameSite()) { + $str .= '; samesite='.$this->getSameSite(); + } + + return $str; + } + + /** + * Gets the name of the cookie. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the cookie. + * + * @return string|null + */ + public function getValue() + { + return $this->value; + } + + /** + * Gets the domain that the cookie is available to. + * + * @return string|null + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Gets the time the cookie expires. + * + * @return int + */ + public function getExpiresTime() + { + return $this->expire; + } + + /** + * Gets the max-age attribute. + * + * @return int + */ + public function getMaxAge() + { + $maxAge = $this->expire - time(); + + return 0 >= $maxAge ? 0 : $maxAge; + } + + /** + * Gets the path on the server in which the cookie will be available on. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client. + * + * @return bool + */ + public function isSecure() + { + return $this->secure ?? $this->secureDefault; + } + + /** + * Checks whether the cookie will be made accessible only through the HTTP protocol. + * + * @return bool + */ + public function isHttpOnly() + { + return $this->httpOnly; + } + + /** + * Whether this cookie is about to be cleared. + * + * @return bool + */ + public function isCleared() + { + return 0 !== $this->expire && $this->expire < time(); + } + + /** + * Checks if the cookie value should be sent with no url encoding. + * + * @return bool + */ + public function isRaw() + { + return $this->raw; + } + + /** + * Gets the SameSite attribute. + * + * @return string|null + */ + public function getSameSite() + { + return $this->sameSite; + } + + /** + * @param bool $default The default value of the "secure" flag when it is set to null + */ + public function setSecureDefault(bool $default): void + { + $this->secureDefault = $default; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Exception/ConflictingHeadersException.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Exception/ConflictingHeadersException.php new file mode 100644 index 00000000..5fcf5b42 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Exception/ConflictingHeadersException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * The HTTP request contains headers with conflicting information. + * + * @author Magnus Nordlander + */ +class ConflictingHeadersException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Exception/RequestExceptionInterface.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Exception/RequestExceptionInterface.php new file mode 100644 index 00000000..478d0dc7 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Exception/RequestExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Interface for Request exceptions. + * + * Exceptions implementing this interface should trigger an HTTP 400 response in the application code. + */ +interface RequestExceptionInterface +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Exception/SuspiciousOperationException.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Exception/SuspiciousOperationException.php new file mode 100644 index 00000000..ae7a5f13 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Exception/SuspiciousOperationException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Raised when a user has performed an operation that should be considered + * suspicious from a security perspective. + */ +class SuspiciousOperationException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/ExpressionRequestMatcher.php b/lam/lib/3rdParty/composer/symfony/http-foundation/ExpressionRequestMatcher.php new file mode 100644 index 00000000..26bed7d3 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/ExpressionRequestMatcher.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; + +/** + * ExpressionRequestMatcher uses an expression to match a Request. + * + * @author Fabien Potencier + */ +class ExpressionRequestMatcher extends RequestMatcher +{ + private $language; + private $expression; + + public function setExpression(ExpressionLanguage $language, $expression) + { + $this->language = $language; + $this->expression = $expression; + } + + public function matches(Request $request) + { + if (!$this->language) { + throw new \LogicException('Unable to match the request as the expression language is not available.'); + } + + return $this->language->evaluate($this->expression, [ + 'request' => $request, + 'method' => $request->getMethod(), + 'path' => rawurldecode($request->getPathInfo()), + 'host' => $request->getHost(), + 'ip' => $request->getClientIp(), + 'attributes' => $request->attributes->all(), + ]) && parent::matches($request); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/AccessDeniedException.php b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/AccessDeniedException.php new file mode 100644 index 00000000..136d2a9f --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/AccessDeniedException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when the access on a file was denied. + * + * @author Bernhard Schussek + */ +class AccessDeniedException extends FileException +{ + public function __construct(string $path) + { + parent::__construct(sprintf('The file %s could not be accessed', $path)); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/CannotWriteFileException.php b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/CannotWriteFileException.php new file mode 100644 index 00000000..c49f53a6 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/CannotWriteFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_CANT_WRITE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class CannotWriteFileException extends FileException +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/ExtensionFileException.php b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/ExtensionFileException.php new file mode 100644 index 00000000..ed83499c --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/ExtensionFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_EXTENSION error occurred with UploadedFile. + * + * @author Florent Mata + */ +class ExtensionFileException extends FileException +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FileException.php b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FileException.php new file mode 100644 index 00000000..fad5133e --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an error occurred in the component File. + * + * @author Bernhard Schussek + */ +class FileException extends \RuntimeException +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FileNotFoundException.php b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FileNotFoundException.php new file mode 100644 index 00000000..31bdf68f --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FileNotFoundException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when a file was not found. + * + * @author Bernhard Schussek + */ +class FileNotFoundException extends FileException +{ + public function __construct(string $path) + { + parent::__construct(sprintf('The file "%s" does not exist', $path)); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FormSizeFileException.php b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FormSizeFileException.php new file mode 100644 index 00000000..8741be08 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FormSizeFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_FORM_SIZE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class FormSizeFileException extends FileException +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/IniSizeFileException.php b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/IniSizeFileException.php new file mode 100644 index 00000000..c8fde610 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/IniSizeFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_INI_SIZE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class IniSizeFileException extends FileException +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/NoFileException.php b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/NoFileException.php new file mode 100644 index 00000000..4b48cc77 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/NoFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_NO_FILE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class NoFileException extends FileException +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/NoTmpDirFileException.php b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/NoTmpDirFileException.php new file mode 100644 index 00000000..bdead2d9 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/NoTmpDirFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_NO_TMP_DIR error occurred with UploadedFile. + * + * @author Florent Mata + */ +class NoTmpDirFileException extends FileException +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/PartialFileException.php b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/PartialFileException.php new file mode 100644 index 00000000..4641efb5 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/PartialFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_PARTIAL error occurred with UploadedFile. + * + * @author Florent Mata + */ +class PartialFileException extends FileException +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/UnexpectedTypeException.php b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/UnexpectedTypeException.php new file mode 100644 index 00000000..82b982b3 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/UnexpectedTypeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +class UnexpectedTypeException extends FileException +{ + public function __construct($value, string $expectedType) + { + parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, \is_object($value) ? \get_class($value) : \gettype($value))); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/UploadException.php b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/UploadException.php new file mode 100644 index 00000000..7074e765 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/UploadException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an error occurred during file upload. + * + * @author Bernhard Schussek + */ +class UploadException extends FileException +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/File/File.php b/lam/lib/3rdParty/composer/symfony/http-foundation/File/File.php new file mode 100644 index 00000000..d79c27f6 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/File/File.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\Mime\MimeTypes; + +/** + * A file in the file system. + * + * @author Bernhard Schussek + */ +class File extends \SplFileInfo +{ + /** + * Constructs a new file from the given path. + * + * @param string $path The path to the file + * @param bool $checkPath Whether to check the path or not + * + * @throws FileNotFoundException If the given path is not a file + */ + public function __construct(string $path, bool $checkPath = true) + { + if ($checkPath && !is_file($path)) { + throw new FileNotFoundException($path); + } + + parent::__construct($path); + } + + /** + * Returns the extension based on the mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getMimeType() + * to guess the file extension. + * + * @return string|null The guessed extension or null if it cannot be guessed + * + * @see MimeTypes + * @see getMimeType() + */ + public function guessExtension() + { + return MimeTypes::getDefault()->getExtensions($this->getMimeType())[0] ?? null; + } + + /** + * Returns the mime type of the file. + * + * The mime type is guessed using a MimeTypeGuesserInterface instance, + * which uses finfo_file() then the "file" system binary, + * depending on which of those are available. + * + * @return string|null The guessed mime type (e.g. "application/pdf") + * + * @see MimeTypes + */ + public function getMimeType() + { + return MimeTypes::getDefault()->guessMimeType($this->getPathname()); + } + + /** + * Moves the file to a new location. + * + * @return self A File object representing the new file + * + * @throws FileException if the target file could not be created + */ + public function move(string $directory, string $name = null) + { + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + $renamed = rename($this->getPathname(), $target); + restore_error_handler(); + if (!$renamed) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + /** + * @return self + */ + protected function getTargetFile(string $directory, string $name = null) + { + if (!is_dir($directory)) { + if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) { + throw new FileException(sprintf('Unable to create the "%s" directory', $directory)); + } + } elseif (!is_writable($directory)) { + throw new FileException(sprintf('Unable to write in the "%s" directory', $directory)); + } + + $target = rtrim($directory, '/\\').\DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name)); + + return new self($target, false); + } + + /** + * Returns locale independent base name of the given path. + * + * @return string + */ + protected function getName(string $name) + { + $originalName = str_replace('\\', '/', $name); + $pos = strrpos($originalName, '/'); + $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1); + + return $originalName; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/File/Stream.php b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Stream.php new file mode 100644 index 00000000..69ae74c1 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/File/Stream.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +/** + * A PHP stream of unknown size. + * + * @author Nicolas Grekas + */ +class Stream extends File +{ + /** + * {@inheritdoc} + */ + public function getSize() + { + return false; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/File/UploadedFile.php b/lam/lib/3rdParty/composer/symfony/http-foundation/File/UploadedFile.php new file mode 100644 index 00000000..3a258e29 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/File/UploadedFile.php @@ -0,0 +1,281 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\CannotWriteFileException; +use Symfony\Component\HttpFoundation\File\Exception\ExtensionFileException; +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException; +use Symfony\Component\HttpFoundation\File\Exception\IniSizeFileException; +use Symfony\Component\HttpFoundation\File\Exception\NoFileException; +use Symfony\Component\HttpFoundation\File\Exception\NoTmpDirFileException; +use Symfony\Component\HttpFoundation\File\Exception\PartialFileException; +use Symfony\Component\Mime\MimeTypes; + +/** + * A file uploaded through a form. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + * @author Fabien Potencier + */ +class UploadedFile extends File +{ + private $test = false; + private $originalName; + private $mimeType; + private $error; + + /** + * Accepts the information of the uploaded file as provided by the PHP global $_FILES. + * + * The file object is only created when the uploaded file is valid (i.e. when the + * isValid() method returns true). Otherwise the only methods that could be called + * on an UploadedFile instance are: + * + * * getClientOriginalName, + * * getClientMimeType, + * * isValid, + * * getError. + * + * Calling any other method on an non-valid instance will cause an unpredictable result. + * + * @param string $path The full temporary path to the file + * @param string $originalName The original file name of the uploaded file + * @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream + * @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK + * @param bool $test Whether the test mode is active + * Local files are used in test mode hence the code should not enforce HTTP uploads + * + * @throws FileException If file_uploads is disabled + * @throws FileNotFoundException If the file does not exist + */ + public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null, bool $test = false) + { + $this->originalName = $this->getName($originalName); + $this->mimeType = $mimeType ?: 'application/octet-stream'; + $this->error = $error ?: UPLOAD_ERR_OK; + $this->test = $test; + + parent::__construct($path, UPLOAD_ERR_OK === $this->error); + } + + /** + * Returns the original file name. + * + * It is extracted from the request from which the file has been uploaded. + * Then it should not be considered as a safe value. + * + * @return string|null The original name + */ + public function getClientOriginalName() + { + return $this->originalName; + } + + /** + * Returns the original file extension. + * + * It is extracted from the original file name that was uploaded. + * Then it should not be considered as a safe value. + * + * @return string The extension + */ + public function getClientOriginalExtension() + { + return pathinfo($this->originalName, PATHINFO_EXTENSION); + } + + /** + * Returns the file mime type. + * + * The client mime type is extracted from the request from which the file + * was uploaded, so it should not be considered as a safe value. + * + * For a trusted mime type, use getMimeType() instead (which guesses the mime + * type based on the file content). + * + * @return string|null The mime type + * + * @see getMimeType() + */ + public function getClientMimeType() + { + return $this->mimeType; + } + + /** + * Returns the extension based on the client mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getClientMimeType() + * to guess the file extension. As such, the extension returned + * by this method cannot be trusted. + * + * For a trusted extension, use guessExtension() instead (which guesses + * the extension based on the guessed mime type for the file). + * + * @return string|null The guessed extension or null if it cannot be guessed + * + * @see guessExtension() + * @see getClientMimeType() + */ + public function guessClientExtension() + { + return MimeTypes::getDefault()->getExtensions($this->getClientMimeType())[0] ?? null; + } + + /** + * Returns the upload error. + * + * If the upload was successful, the constant UPLOAD_ERR_OK is returned. + * Otherwise one of the other UPLOAD_ERR_XXX constants is returned. + * + * @return int The upload error + */ + public function getError() + { + return $this->error; + } + + /** + * Returns whether the file was uploaded successfully. + * + * @return bool True if the file has been uploaded with HTTP and no error occurred + */ + public function isValid() + { + $isOk = UPLOAD_ERR_OK === $this->error; + + return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname()); + } + + /** + * Moves the file to a new location. + * + * @return File A File object representing the new file + * + * @throws FileException if, for any reason, the file could not have been moved + */ + public function move(string $directory, string $name = null) + { + if ($this->isValid()) { + if ($this->test) { + return parent::move($directory, $name); + } + + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + $moved = move_uploaded_file($this->getPathname(), $target); + restore_error_handler(); + if (!$moved) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + switch ($this->error) { + case UPLOAD_ERR_INI_SIZE: + throw new IniSizeFileException($this->getErrorMessage()); + case UPLOAD_ERR_FORM_SIZE: + throw new FormSizeFileException($this->getErrorMessage()); + case UPLOAD_ERR_PARTIAL: + throw new PartialFileException($this->getErrorMessage()); + case UPLOAD_ERR_NO_FILE: + throw new NoFileException($this->getErrorMessage()); + case UPLOAD_ERR_CANT_WRITE: + throw new CannotWriteFileException($this->getErrorMessage()); + case UPLOAD_ERR_NO_TMP_DIR: + throw new NoTmpDirFileException($this->getErrorMessage()); + case UPLOAD_ERR_EXTENSION: + throw new ExtensionFileException($this->getErrorMessage()); + } + + throw new FileException($this->getErrorMessage()); + } + + /** + * Returns the maximum size of an uploaded file as configured in php.ini. + * + * @return int The maximum size of an uploaded file in bytes + */ + public static function getMaxFilesize() + { + $sizePostMax = self::parseFilesize(ini_get('post_max_size')); + $sizeUploadMax = self::parseFilesize(ini_get('upload_max_filesize')); + + return min($sizePostMax ?: PHP_INT_MAX, $sizeUploadMax ?: PHP_INT_MAX); + } + + /** + * Returns the given size from an ini value in bytes. + */ + private static function parseFilesize($size): int + { + if ('' === $size) { + return 0; + } + + $size = strtolower($size); + + $max = ltrim($size, '+'); + if (0 === strpos($max, '0x')) { + $max = \intval($max, 16); + } elseif (0 === strpos($max, '0')) { + $max = \intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($size, -1)) { + case 't': $max *= 1024; + // no break + case 'g': $max *= 1024; + // no break + case 'm': $max *= 1024; + // no break + case 'k': $max *= 1024; + } + + return $max; + } + + /** + * Returns an informative upload error message. + * + * @return string The error message regarding the specified error code + */ + public function getErrorMessage() + { + static $errors = [ + UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).', + UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.', + UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.', + UPLOAD_ERR_NO_FILE => 'No file was uploaded.', + UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.', + UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.', + UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.', + ]; + + $errorCode = $this->error; + $maxFilesize = UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0; + $message = isset($errors[$errorCode]) ? $errors[$errorCode] : 'The file "%s" was not uploaded due to an unknown error.'; + + return sprintf($message, $this->getClientOriginalName(), $maxFilesize); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/FileBag.php b/lam/lib/3rdParty/composer/symfony/http-foundation/FileBag.php new file mode 100644 index 00000000..5edd372b --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/FileBag.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\UploadedFile; + +/** + * FileBag is a container for uploaded files. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + */ +class FileBag extends ParameterBag +{ + private static $fileKeys = ['error', 'name', 'size', 'tmp_name', 'type']; + + /** + * @param array|UploadedFile[] $parameters An array of HTTP files + */ + public function __construct(array $parameters = []) + { + $this->replace($parameters); + } + + /** + * {@inheritdoc} + */ + public function replace(array $files = []) + { + $this->parameters = []; + $this->add($files); + } + + /** + * {@inheritdoc} + */ + public function set(string $key, $value) + { + if (!\is_array($value) && !$value instanceof UploadedFile) { + throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.'); + } + + parent::set($key, $this->convertFileInformation($value)); + } + + /** + * {@inheritdoc} + */ + public function add(array $files = []) + { + foreach ($files as $key => $file) { + $this->set($key, $file); + } + } + + /** + * Converts uploaded files to UploadedFile instances. + * + * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information + * + * @return UploadedFile[]|UploadedFile|null A (multi-dimensional) array of UploadedFile instances + */ + protected function convertFileInformation($file) + { + if ($file instanceof UploadedFile) { + return $file; + } + + if (\is_array($file)) { + $file = $this->fixPhpFilesArray($file); + $keys = array_keys($file); + sort($keys); + + if ($keys == self::$fileKeys) { + if (UPLOAD_ERR_NO_FILE == $file['error']) { + $file = null; + } else { + $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error'], false); + } + } else { + $file = array_map([$this, 'convertFileInformation'], $file); + if (array_keys($keys) === $keys) { + $file = array_filter($file); + } + } + } + + return $file; + } + + /** + * Fixes a malformed PHP $_FILES array. + * + * PHP has a bug that the format of the $_FILES array differs, depending on + * whether the uploaded file fields had normal field names or array-like + * field names ("normal" vs. "parent[child]"). + * + * This method fixes the array to look like the "normal" $_FILES array. + * + * It's safe to pass an already converted array, in which case this method + * just returns the original array unmodified. + * + * @param array $data + * + * @return array + */ + protected function fixPhpFilesArray($data) + { + $keys = array_keys($data); + sort($keys); + + if (self::$fileKeys != $keys || !isset($data['name']) || !\is_array($data['name'])) { + return $data; + } + + $files = $data; + foreach (self::$fileKeys as $k) { + unset($files[$k]); + } + + foreach ($data['name'] as $key => $name) { + $files[$key] = $this->fixPhpFilesArray([ + 'error' => $data['error'][$key], + 'name' => $name, + 'type' => $data['type'][$key], + 'tmp_name' => $data['tmp_name'][$key], + 'size' => $data['size'][$key], + ]); + } + + return $files; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/HeaderBag.php b/lam/lib/3rdParty/composer/symfony/http-foundation/HeaderBag.php new file mode 100644 index 00000000..9c7273a7 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/HeaderBag.php @@ -0,0 +1,288 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * HeaderBag is a container for HTTP headers. + * + * @author Fabien Potencier + */ +class HeaderBag implements \IteratorAggregate, \Countable +{ + protected const UPPER = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + protected const LOWER = '-abcdefghijklmnopqrstuvwxyz'; + + protected $headers = []; + protected $cacheControl = []; + + public function __construct(array $headers = []) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns the headers as a string. + * + * @return string The headers + */ + public function __toString() + { + if (!$headers = $this->all()) { + return ''; + } + + ksort($headers); + $max = max(array_map('strlen', array_keys($headers))) + 1; + $content = ''; + foreach ($headers as $name => $values) { + $name = ucwords($name, '-'); + foreach ($values as $value) { + $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value); + } + } + + return $content; + } + + /** + * Returns the headers. + * + * @param string|null $key The name of the headers to return or null to get them all + * + * @return array An array of headers + */ + public function all(string $key = null) + { + if (null !== $key) { + return $this->headers[strtr($key, self::UPPER, self::LOWER)] ?? []; + } + + return $this->headers; + } + + /** + * Returns the parameter keys. + * + * @return array An array of parameter keys + */ + public function keys() + { + return array_keys($this->all()); + } + + /** + * Replaces the current HTTP headers by a new set. + */ + public function replace(array $headers = []) + { + $this->headers = []; + $this->add($headers); + } + + /** + * Adds new headers the current HTTP headers set. + */ + public function add(array $headers) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns a header value by name. + * + * @return string|null The first header value or default value + */ + public function get(string $key, string $default = null) + { + $headers = $this->all($key); + + if (!$headers) { + return $default; + } + + if (null === $headers[0]) { + return null; + } + + return (string) $headers[0]; + } + + /** + * Sets a header by name. + * + * @param string|string[] $values The value or an array of values + * @param bool $replace Whether to replace the actual value or not (true by default) + */ + public function set(string $key, $values, bool $replace = true) + { + $key = strtr($key, self::UPPER, self::LOWER); + + if (\is_array($values)) { + $values = array_values($values); + + if (true === $replace || !isset($this->headers[$key])) { + $this->headers[$key] = $values; + } else { + $this->headers[$key] = array_merge($this->headers[$key], $values); + } + } else { + if (true === $replace || !isset($this->headers[$key])) { + $this->headers[$key] = [$values]; + } else { + $this->headers[$key][] = $values; + } + } + + if ('cache-control' === $key) { + $this->cacheControl = $this->parseCacheControl(implode(', ', $this->headers[$key])); + } + } + + /** + * Returns true if the HTTP header is defined. + * + * @return bool true if the parameter exists, false otherwise + */ + public function has(string $key) + { + return \array_key_exists(strtr($key, self::UPPER, self::LOWER), $this->all()); + } + + /** + * Returns true if the given HTTP header contains the given value. + * + * @return bool true if the value is contained in the header, false otherwise + */ + public function contains(string $key, string $value) + { + return \in_array($value, $this->all($key)); + } + + /** + * Removes a header. + */ + public function remove(string $key) + { + $key = strtr($key, self::UPPER, self::LOWER); + + unset($this->headers[$key]); + + if ('cache-control' === $key) { + $this->cacheControl = []; + } + } + + /** + * Returns the HTTP header value converted to a date. + * + * @return \DateTimeInterface|null The parsed DateTime or the default value if the header does not exist + * + * @throws \RuntimeException When the HTTP header is not parseable + */ + public function getDate(string $key, \DateTime $default = null) + { + if (null === $value = $this->get($key)) { + return $default; + } + + if (false === $date = \DateTime::createFromFormat(DATE_RFC2822, $value)) { + throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value)); + } + + return $date; + } + + /** + * Adds a custom Cache-Control directive. + * + * @param mixed $value The Cache-Control directive value + */ + public function addCacheControlDirective(string $key, $value = true) + { + $this->cacheControl[$key] = $value; + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns true if the Cache-Control directive is defined. + * + * @return bool true if the directive exists, false otherwise + */ + public function hasCacheControlDirective(string $key) + { + return \array_key_exists($key, $this->cacheControl); + } + + /** + * Returns a Cache-Control directive value by name. + * + * @return mixed|null The directive value if defined, null otherwise + */ + public function getCacheControlDirective(string $key) + { + return \array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null; + } + + /** + * Removes a Cache-Control directive. + */ + public function removeCacheControlDirective(string $key) + { + unset($this->cacheControl[$key]); + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns an iterator for headers. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->headers); + } + + /** + * Returns the number of headers. + * + * @return int The number of headers + */ + public function count() + { + return \count($this->headers); + } + + protected function getCacheControlHeader() + { + ksort($this->cacheControl); + + return HeaderUtils::toString($this->cacheControl, ','); + } + + /** + * Parses a Cache-Control HTTP header. + * + * @return array An array representing the attribute values + */ + protected function parseCacheControl(string $header) + { + $parts = HeaderUtils::split($header, ',='); + + return HeaderUtils::combine($parts); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/HeaderUtils.php b/lam/lib/3rdParty/composer/symfony/http-foundation/HeaderUtils.php new file mode 100644 index 00000000..5866e3b2 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/HeaderUtils.php @@ -0,0 +1,224 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * HTTP header utility functions. + * + * @author Christian Schmidt + */ +class HeaderUtils +{ + public const DISPOSITION_ATTACHMENT = 'attachment'; + public const DISPOSITION_INLINE = 'inline'; + + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Splits an HTTP header by one or more separators. + * + * Example: + * + * HeaderUtils::split("da, en-gb;q=0.8", ",;") + * // => ['da'], ['en-gb', 'q=0.8']] + * + * @param string $separators List of characters to split on, ordered by + * precedence, e.g. ",", ";=", or ",;=" + * + * @return array Nested array with as many levels as there are characters in + * $separators + */ + public static function split(string $header, string $separators): array + { + $quotedSeparators = preg_quote($separators, '/'); + + preg_match_all(' + / + (?!\s) + (?: + # quoted-string + "(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$) + | + # token + [^"'.$quotedSeparators.']+ + )+ + (?['.$quotedSeparators.']) + \s* + /x', trim($header), $matches, PREG_SET_ORDER); + + return self::groupParts($matches, $separators); + } + + /** + * Combines an array of arrays into one associative array. + * + * Each of the nested arrays should have one or two elements. The first + * value will be used as the keys in the associative array, and the second + * will be used as the values, or true if the nested array only contains one + * element. Array keys are lowercased. + * + * Example: + * + * HeaderUtils::combine([["foo", "abc"], ["bar"]]) + * // => ["foo" => "abc", "bar" => true] + */ + public static function combine(array $parts): array + { + $assoc = []; + foreach ($parts as $part) { + $name = strtolower($part[0]); + $value = $part[1] ?? true; + $assoc[$name] = $value; + } + + return $assoc; + } + + /** + * Joins an associative array into a string for use in an HTTP header. + * + * The key and value of each entry are joined with "=", and all entries + * are joined with the specified separator and an additional space (for + * readability). Values are quoted if necessary. + * + * Example: + * + * HeaderUtils::toString(["foo" => "abc", "bar" => true, "baz" => "a b c"], ",") + * // => 'foo=abc, bar, baz="a b c"' + */ + public static function toString(array $assoc, string $separator): string + { + $parts = []; + foreach ($assoc as $name => $value) { + if (true === $value) { + $parts[] = $name; + } else { + $parts[] = $name.'='.self::quote($value); + } + } + + return implode($separator.' ', $parts); + } + + /** + * Encodes a string as a quoted string, if necessary. + * + * If a string contains characters not allowed by the "token" construct in + * the HTTP specification, it is backslash-escaped and enclosed in quotes + * to match the "quoted-string" construct. + */ + public static function quote(string $s): string + { + if (preg_match('/^[a-z0-9!#$%&\'*.^_`|~-]+$/i', $s)) { + return $s; + } + + return '"'.addcslashes($s, '"\\"').'"'; + } + + /** + * Decodes a quoted string. + * + * If passed an unquoted string that matches the "token" construct (as + * defined in the HTTP specification), it is passed through verbatimly. + */ + public static function unquote(string $s): string + { + return preg_replace('/\\\\(.)|"/', '$1', $s); + } + + /** + * Generates a HTTP Content-Disposition field-value. + * + * @param string $disposition One of "inline" or "attachment" + * @param string $filename A unicode string + * @param string $filenameFallback A string containing only ASCII characters that + * is semantically equivalent to $filename. If the filename is already ASCII, + * it can be omitted, or just copied from $filename + * + * @return string A string suitable for use as a Content-Disposition field-value + * + * @throws \InvalidArgumentException + * + * @see RFC 6266 + */ + public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string + { + if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) { + throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); + } + + if ('' === $filenameFallback) { + $filenameFallback = $filename; + } + + // filenameFallback is not ASCII. + if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) { + throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.'); + } + + // percent characters aren't safe in fallback. + if (false !== strpos($filenameFallback, '%')) { + throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.'); + } + + // path separators aren't allowed in either. + if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) { + throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.'); + } + + $params = ['filename' => $filenameFallback]; + if ($filename !== $filenameFallback) { + $params['filename*'] = "utf-8''".rawurlencode($filename); + } + + return $disposition.'; '.self::toString($params, ';'); + } + + private static function groupParts(array $matches, string $separators): array + { + $separator = $separators[0]; + $partSeparators = substr($separators, 1); + + $i = 0; + $partMatches = []; + foreach ($matches as $match) { + if (isset($match['separator']) && $match['separator'] === $separator) { + ++$i; + } else { + $partMatches[$i][] = $match; + } + } + + $parts = []; + if ($partSeparators) { + foreach ($partMatches as $matches) { + $parts[] = self::groupParts($matches, $partSeparators); + } + } else { + foreach ($partMatches as $matches) { + $parts[] = self::unquote($matches[0][0]); + } + } + + return $parts; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/IpUtils.php b/lam/lib/3rdParty/composer/symfony/http-foundation/IpUtils.php new file mode 100644 index 00000000..80c5a950 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/IpUtils.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Http utility functions. + * + * @author Fabien Potencier + */ +class IpUtils +{ + private static $checkedIps = []; + + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets. + * + * @param string|array $ips List of IPs or subnets (can be a string if only a single one) + * + * @return bool Whether the IP is valid + */ + public static function checkIp(?string $requestIp, $ips) + { + if (!\is_array($ips)) { + $ips = [$ips]; + } + + $method = substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4'; + + foreach ($ips as $ip) { + if (self::$method($requestIp, $ip)) { + return true; + } + } + + return false; + } + + /** + * Compares two IPv4 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @param string $ip IPv4 address or subnet in CIDR notation + * + * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet + */ + public static function checkIp4(?string $requestIp, string $ip) + { + $cacheKey = $requestIp.'-'.$ip; + if (isset(self::$checkedIps[$cacheKey])) { + return self::$checkedIps[$cacheKey]; + } + + if (!filter_var($requestIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + return self::$checkedIps[$cacheKey] = false; + } + + if (false !== strpos($ip, '/')) { + list($address, $netmask) = explode('/', $ip, 2); + + if ('0' === $netmask) { + return self::$checkedIps[$cacheKey] = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); + } + + if ($netmask < 0 || $netmask > 32) { + return self::$checkedIps[$cacheKey] = false; + } + } else { + $address = $ip; + $netmask = 32; + } + + if (false === ip2long($address)) { + return self::$checkedIps[$cacheKey] = false; + } + + return self::$checkedIps[$cacheKey] = 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask); + } + + /** + * Compares two IPv6 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @author David Soria Parra + * + * @see https://github.com/dsp/v6tools + * + * @param string $ip IPv6 address or subnet in CIDR notation + * + * @return bool Whether the IP is valid + * + * @throws \RuntimeException When IPV6 support is not enabled + */ + public static function checkIp6(?string $requestIp, string $ip) + { + $cacheKey = $requestIp.'-'.$ip; + if (isset(self::$checkedIps[$cacheKey])) { + return self::$checkedIps[$cacheKey]; + } + + if (!((\extension_loaded('sockets') && \defined('AF_INET6')) || @inet_pton('::1'))) { + throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); + } + + if (false !== strpos($ip, '/')) { + list($address, $netmask) = explode('/', $ip, 2); + + if ('0' === $netmask) { + return (bool) unpack('n*', @inet_pton($address)); + } + + if ($netmask < 1 || $netmask > 128) { + return self::$checkedIps[$cacheKey] = false; + } + } else { + $address = $ip; + $netmask = 128; + } + + $bytesAddr = unpack('n*', @inet_pton($address)); + $bytesTest = unpack('n*', @inet_pton($requestIp)); + + if (!$bytesAddr || !$bytesTest) { + return self::$checkedIps[$cacheKey] = false; + } + + for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) { + $left = $netmask - 16 * ($i - 1); + $left = ($left <= 16) ? $left : 16; + $mask = ~(0xffff >> $left) & 0xffff; + if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { + return self::$checkedIps[$cacheKey] = false; + } + } + + return self::$checkedIps[$cacheKey] = true; + } + + /** + * Anonymizes an IP/IPv6. + * + * Removes the last byte for v4 and the last 8 bytes for v6 IPs + */ + public static function anonymize(string $ip): string + { + $wrappedIPv6 = false; + if ('[' === substr($ip, 0, 1) && ']' === substr($ip, -1, 1)) { + $wrappedIPv6 = true; + $ip = substr($ip, 1, -1); + } + + $packedAddress = inet_pton($ip); + if (4 === \strlen($packedAddress)) { + $mask = '255.255.255.0'; + } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff:ffff'))) { + $mask = '::ffff:ffff:ff00'; + } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff'))) { + $mask = '::ffff:ff00'; + } else { + $mask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; + } + $ip = inet_ntop($packedAddress & inet_pton($mask)); + + if ($wrappedIPv6) { + $ip = '['.$ip.']'; + } + + return $ip; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/JsonResponse.php b/lam/lib/3rdParty/composer/symfony/http-foundation/JsonResponse.php new file mode 100644 index 00000000..8489bc0b --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/JsonResponse.php @@ -0,0 +1,215 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Response represents an HTTP response in JSON format. + * + * Note that this class does not force the returned JSON content to be an + * object. It is however recommended that you do return an object as it + * protects yourself against XSSI and JSON-JavaScript Hijacking. + * + * @see https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/AJAX_Security_Cheat_Sheet.md#always-return-json-with-an-object-on-the-outside + * + * @author Igor Wiedler + */ +class JsonResponse extends Response +{ + protected $data; + protected $callback; + + // Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML. + // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT + const DEFAULT_ENCODING_OPTIONS = 15; + + protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS; + + /** + * @param mixed $data The response data + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $json If the data is already a JSON string + */ + public function __construct($data = null, int $status = 200, array $headers = [], bool $json = false) + { + parent::__construct('', $status, $headers); + + if (null === $data) { + $data = new \ArrayObject(); + } + + $json ? $this->setJson($data) : $this->setData($data); + } + + /** + * Factory method for chainability. + * + * Example: + * + * return JsonResponse::create(['key' => 'value']) + * ->setSharedMaxAge(300); + * + * @param mixed $data The JSON response data + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + */ + public static function create($data = null, int $status = 200, array $headers = []) + { + return new static($data, $status, $headers); + } + + /** + * Factory method for chainability. + * + * Example: + * + * return JsonResponse::fromJsonString('{"key": "value"}') + * ->setSharedMaxAge(300); + * + * @param string|null $data The JSON response string + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + */ + public static function fromJsonString(string $data = null, int $status = 200, array $headers = []) + { + return new static($data, $status, $headers, true); + } + + /** + * Sets the JSONP callback. + * + * @param string|null $callback The JSONP callback or null to use none + * + * @return $this + * + * @throws \InvalidArgumentException When the callback name is not valid + */ + public function setCallback(string $callback = null) + { + if (null !== $callback) { + // partially taken from https://geekality.net/2011/08/03/valid-javascript-identifier/ + // partially taken from https://github.com/willdurand/JsonpCallbackValidator + // JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details. + // (c) William Durand + $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u'; + $reserved = [ + 'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while', + 'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export', + 'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false', + ]; + $parts = explode('.', $callback); + foreach ($parts as $part) { + if (!preg_match($pattern, $part) || \in_array($part, $reserved, true)) { + throw new \InvalidArgumentException('The callback name is not valid.'); + } + } + } + + $this->callback = $callback; + + return $this->update(); + } + + /** + * Sets a raw string containing a JSON document to be sent. + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setJson(string $json) + { + $this->data = $json; + + return $this->update(); + } + + /** + * Sets the data to be sent as JSON. + * + * @param mixed $data + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setData($data = []) + { + try { + $data = json_encode($data, $this->encodingOptions); + } catch (\Exception $e) { + if ('Exception' === \get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) { + throw $e->getPrevious() ?: $e; + } + throw $e; + } + + if (\PHP_VERSION_ID >= 70300 && (JSON_THROW_ON_ERROR & $this->encodingOptions)) { + return $this->setJson($data); + } + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $this->setJson($data); + } + + /** + * Returns options used while encoding data to JSON. + * + * @return int + */ + public function getEncodingOptions() + { + return $this->encodingOptions; + } + + /** + * Sets options used while encoding data to JSON. + * + * @return $this + */ + public function setEncodingOptions(int $encodingOptions) + { + $this->encodingOptions = $encodingOptions; + + return $this->setData(json_decode($this->data)); + } + + /** + * Updates the content and headers according to the JSON data and callback. + * + * @return $this + */ + protected function update() + { + if (null !== $this->callback) { + // Not using application/javascript for compatibility reasons with older browsers. + $this->headers->set('Content-Type', 'text/javascript'); + + return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data)); + } + + // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback) + // in order to not overwrite a custom definition. + if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) { + $this->headers->set('Content-Type', 'application/json'); + } + + return $this->setContent($this->data); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/LICENSE b/lam/lib/3rdParty/composer/symfony/http-foundation/LICENSE new file mode 100644 index 00000000..a677f437 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/ParameterBag.php b/lam/lib/3rdParty/composer/symfony/http-foundation/ParameterBag.php new file mode 100644 index 00000000..212149c7 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/ParameterBag.php @@ -0,0 +1,205 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ParameterBag is a container for key/value pairs. + * + * @author Fabien Potencier + */ +class ParameterBag implements \IteratorAggregate, \Countable +{ + /** + * Parameter storage. + */ + protected $parameters; + + public function __construct(array $parameters = []) + { + $this->parameters = $parameters; + } + + /** + * Returns the parameters. + * + * @return array An array of parameters + */ + public function all() + { + return $this->parameters; + } + + /** + * Returns the parameter keys. + * + * @return array An array of parameter keys + */ + public function keys() + { + return array_keys($this->parameters); + } + + /** + * Replaces the current parameters by a new set. + */ + public function replace(array $parameters = []) + { + $this->parameters = $parameters; + } + + /** + * Adds parameters. + */ + public function add(array $parameters = []) + { + $this->parameters = array_replace($this->parameters, $parameters); + } + + /** + * Returns a parameter by name. + * + * @param mixed $default The default value if the parameter key does not exist + * + * @return mixed + */ + public function get(string $key, $default = null) + { + return \array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default; + } + + /** + * Sets a parameter by name. + * + * @param mixed $value The value + */ + public function set(string $key, $value) + { + $this->parameters[$key] = $value; + } + + /** + * Returns true if the parameter is defined. + * + * @return bool true if the parameter exists, false otherwise + */ + public function has(string $key) + { + return \array_key_exists($key, $this->parameters); + } + + /** + * Removes a parameter. + */ + public function remove(string $key) + { + unset($this->parameters[$key]); + } + + /** + * Returns the alphabetic characters of the parameter value. + * + * @return string The filtered value + */ + public function getAlpha(string $key, string $default = '') + { + return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default)); + } + + /** + * Returns the alphabetic characters and digits of the parameter value. + * + * @return string The filtered value + */ + public function getAlnum(string $key, string $default = '') + { + return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default)); + } + + /** + * Returns the digits of the parameter value. + * + * @return string The filtered value + */ + public function getDigits(string $key, string $default = '') + { + // we need to remove - and + because they're allowed in the filter + return str_replace(['-', '+'], '', $this->filter($key, $default, FILTER_SANITIZE_NUMBER_INT)); + } + + /** + * Returns the parameter value converted to integer. + * + * @return int The filtered value + */ + public function getInt(string $key, int $default = 0) + { + return (int) $this->get($key, $default); + } + + /** + * Returns the parameter value converted to boolean. + * + * @return bool The filtered value + */ + public function getBoolean(string $key, bool $default = false) + { + return $this->filter($key, $default, FILTER_VALIDATE_BOOLEAN); + } + + /** + * Filter key. + * + * @param mixed $default Default = null + * @param int $filter FILTER_* constant + * @param mixed $options Filter options + * + * @see https://php.net/filter-var + * + * @return mixed + */ + public function filter(string $key, $default = null, int $filter = FILTER_DEFAULT, $options = []) + { + $value = $this->get($key, $default); + + // Always turn $options into an array - this allows filter_var option shortcuts. + if (!\is_array($options) && $options) { + $options = ['flags' => $options]; + } + + // Add a convenience check for arrays. + if (\is_array($value) && !isset($options['flags'])) { + $options['flags'] = FILTER_REQUIRE_ARRAY; + } + + return filter_var($value, $filter, $options); + } + + /** + * Returns an iterator for parameters. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->parameters); + } + + /** + * Returns the number of parameters. + * + * @return int The number of parameters + */ + public function count() + { + return \count($this->parameters); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/README.md b/lam/lib/3rdParty/composer/symfony/http-foundation/README.md new file mode 100644 index 00000000..8907f0b9 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/README.md @@ -0,0 +1,14 @@ +HttpFoundation Component +======================== + +The HttpFoundation component defines an object-oriented layer for the HTTP +specification. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/http_foundation/index.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/RedirectResponse.php b/lam/lib/3rdParty/composer/symfony/http-foundation/RedirectResponse.php new file mode 100644 index 00000000..13da56a7 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/RedirectResponse.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RedirectResponse represents an HTTP response doing a redirect. + * + * @author Fabien Potencier + */ +class RedirectResponse extends Response +{ + protected $targetUrl; + + /** + * Creates a redirect response so that it conforms to the rules defined for a redirect status code. + * + * @param string $url The URL to redirect to. The URL should be a full URL, with schema etc., + * but practically every browser redirects on paths only as well + * @param int $status The status code (302 by default) + * @param array $headers The headers (Location is always set to the given URL) + * + * @throws \InvalidArgumentException + * + * @see https://tools.ietf.org/html/rfc2616#section-10.3 + */ + public function __construct(string $url, int $status = 302, array $headers = []) + { + parent::__construct('', $status, $headers); + + $this->setTargetUrl($url); + + if (!$this->isRedirect()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status)); + } + + if (301 == $status && !\array_key_exists('cache-control', array_change_key_case($headers, \CASE_LOWER))) { + $this->headers->remove('cache-control'); + } + } + + /** + * Factory method for chainability. + * + * @param string $url The URL to redirect to + * + * @return static + */ + public static function create($url = '', int $status = 302, array $headers = []) + { + return new static($url, $status, $headers); + } + + /** + * Returns the target URL. + * + * @return string target URL + */ + public function getTargetUrl() + { + return $this->targetUrl; + } + + /** + * Sets the redirect target of this response. + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setTargetUrl(string $url) + { + if ('' === $url) { + throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); + } + + $this->targetUrl = $url; + + $this->setContent( + sprintf(' + + + + + + Redirecting to %1$s + + + Redirecting to %1$s. + +', htmlspecialchars($url, ENT_QUOTES, 'UTF-8'))); + + $this->headers->set('Location', $url); + + return $this; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Request.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Request.php new file mode 100644 index 00000000..1ab74d7e --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Request.php @@ -0,0 +1,2060 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Request represents an HTTP request. + * + * The methods dealing with URL accept / return a raw path (% encoded): + * * getBasePath + * * getBaseUrl + * * getPathInfo + * * getRequestUri + * * getUri + * * getUriForPath + * + * @author Fabien Potencier + */ +class Request +{ + const HEADER_FORWARDED = 0b00001; // When using RFC 7239 + const HEADER_X_FORWARDED_FOR = 0b00010; + const HEADER_X_FORWARDED_HOST = 0b00100; + const HEADER_X_FORWARDED_PROTO = 0b01000; + const HEADER_X_FORWARDED_PORT = 0b10000; + const HEADER_X_FORWARDED_ALL = 0b11110; // All "X-Forwarded-*" headers + const HEADER_X_FORWARDED_AWS_ELB = 0b11010; // AWS ELB doesn't send X-Forwarded-Host + + const METHOD_HEAD = 'HEAD'; + const METHOD_GET = 'GET'; + const METHOD_POST = 'POST'; + const METHOD_PUT = 'PUT'; + const METHOD_PATCH = 'PATCH'; + const METHOD_DELETE = 'DELETE'; + const METHOD_PURGE = 'PURGE'; + const METHOD_OPTIONS = 'OPTIONS'; + const METHOD_TRACE = 'TRACE'; + const METHOD_CONNECT = 'CONNECT'; + + /** + * @var string[] + */ + protected static $trustedProxies = []; + + /** + * @var string[] + */ + protected static $trustedHostPatterns = []; + + /** + * @var string[] + */ + protected static $trustedHosts = []; + + protected static $httpMethodParameterOverride = false; + + /** + * Custom parameters. + * + * @var ParameterBag + */ + public $attributes; + + /** + * Request body parameters ($_POST). + * + * @var ParameterBag + */ + public $request; + + /** + * Query string parameters ($_GET). + * + * @var ParameterBag + */ + public $query; + + /** + * Server and execution environment parameters ($_SERVER). + * + * @var ServerBag + */ + public $server; + + /** + * Uploaded files ($_FILES). + * + * @var FileBag + */ + public $files; + + /** + * Cookies ($_COOKIE). + * + * @var ParameterBag + */ + public $cookies; + + /** + * Headers (taken from the $_SERVER). + * + * @var HeaderBag + */ + public $headers; + + /** + * @var string|resource|false|null + */ + protected $content; + + /** + * @var array + */ + protected $languages; + + /** + * @var array + */ + protected $charsets; + + /** + * @var array + */ + protected $encodings; + + /** + * @var array + */ + protected $acceptableContentTypes; + + /** + * @var string + */ + protected $pathInfo; + + /** + * @var string + */ + protected $requestUri; + + /** + * @var string + */ + protected $baseUrl; + + /** + * @var string + */ + protected $basePath; + + /** + * @var string + */ + protected $method; + + /** + * @var string + */ + protected $format; + + /** + * @var SessionInterface + */ + protected $session; + + /** + * @var string + */ + protected $locale; + + /** + * @var string + */ + protected $defaultLocale = 'en'; + + /** + * @var array + */ + protected static $formats; + + protected static $requestFactory; + + /** + * @var string|null + */ + private $preferredFormat; + private $isHostValid = true; + private $isForwardedValid = true; + + private static $trustedHeaderSet = -1; + + private static $forwardedParams = [ + self::HEADER_X_FORWARDED_FOR => 'for', + self::HEADER_X_FORWARDED_HOST => 'host', + self::HEADER_X_FORWARDED_PROTO => 'proto', + self::HEADER_X_FORWARDED_PORT => 'host', + ]; + + /** + * Names for headers that can be trusted when + * using trusted proxies. + * + * The FORWARDED header is the standard as of rfc7239. + * + * The other headers are non-standard, but widely used + * by popular reverse proxies (like Apache mod_proxy or Amazon EC2). + */ + private static $trustedHeaders = [ + self::HEADER_FORWARDED => 'FORWARDED', + self::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR', + self::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST', + self::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO', + self::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT', + ]; + + /** + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource|null $content The raw body data + */ + public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) + { + $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Sets the parameters for this request. + * + * This method also re-initializes all properties. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource|null $content The raw body data + */ + public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) + { + $this->request = new ParameterBag($request); + $this->query = new ParameterBag($query); + $this->attributes = new ParameterBag($attributes); + $this->cookies = new ParameterBag($cookies); + $this->files = new FileBag($files); + $this->server = new ServerBag($server); + $this->headers = new HeaderBag($this->server->getHeaders()); + + $this->content = $content; + $this->languages = null; + $this->charsets = null; + $this->encodings = null; + $this->acceptableContentTypes = null; + $this->pathInfo = null; + $this->requestUri = null; + $this->baseUrl = null; + $this->basePath = null; + $this->method = null; + $this->format = null; + } + + /** + * Creates a new request with values from PHP's super globals. + * + * @return static + */ + public static function createFromGlobals() + { + $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); + + if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded') + && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH']) + ) { + parse_str($request->getContent(), $data); + $request->request = new ParameterBag($data); + } + + return $request; + } + + /** + * Creates a Request based on a given URI and configuration. + * + * The information contained in the URI always take precedence + * over the other information (server and parameters). + * + * @param string $uri The URI + * @param string $method The HTTP method + * @param array $parameters The query (GET) or request (POST) parameters + * @param array $cookies The request cookies ($_COOKIE) + * @param array $files The request files ($_FILES) + * @param array $server The server parameters ($_SERVER) + * @param string|resource|null $content The raw body data + * + * @return static + */ + public static function create(string $uri, string $method = 'GET', array $parameters = [], array $cookies = [], array $files = [], array $server = [], $content = null) + { + $server = array_replace([ + 'SERVER_NAME' => 'localhost', + 'SERVER_PORT' => 80, + 'HTTP_HOST' => 'localhost', + 'HTTP_USER_AGENT' => 'Symfony', + 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'REMOTE_ADDR' => '127.0.0.1', + 'SCRIPT_NAME' => '', + 'SCRIPT_FILENAME' => '', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_TIME' => time(), + ], $server); + + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + + $components = parse_url($uri); + if (isset($components['host'])) { + $server['SERVER_NAME'] = $components['host']; + $server['HTTP_HOST'] = $components['host']; + } + + if (isset($components['scheme'])) { + if ('https' === $components['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + + if (isset($components['port'])) { + $server['SERVER_PORT'] = $components['port']; + $server['HTTP_HOST'] .= ':'.$components['port']; + } + + if (isset($components['user'])) { + $server['PHP_AUTH_USER'] = $components['user']; + } + + if (isset($components['pass'])) { + $server['PHP_AUTH_PW'] = $components['pass']; + } + + if (!isset($components['path'])) { + $components['path'] = '/'; + } + + switch (strtoupper($method)) { + case 'POST': + case 'PUT': + case 'DELETE': + if (!isset($server['CONTENT_TYPE'])) { + $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + } + // no break + case 'PATCH': + $request = $parameters; + $query = []; + break; + default: + $request = []; + $query = $parameters; + break; + } + + $queryString = ''; + if (isset($components['query'])) { + parse_str(html_entity_decode($components['query']), $qs); + + if ($query) { + $query = array_replace($qs, $query); + $queryString = http_build_query($query, '', '&'); + } else { + $query = $qs; + $queryString = $components['query']; + } + } elseif ($query) { + $queryString = http_build_query($query, '', '&'); + } + + $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : ''); + $server['QUERY_STRING'] = $queryString; + + return self::createRequestFromFactory($query, $request, [], $cookies, $files, $server, $content); + } + + /** + * Sets a callable able to create a Request instance. + * + * This is mainly useful when you need to override the Request class + * to keep BC with an existing system. It should not be used for any + * other purpose. + */ + public static function setFactory(?callable $callable) + { + self::$requestFactory = $callable; + } + + /** + * Clones a request and overrides some of its parameters. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * + * @return static + */ + public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) + { + $dup = clone $this; + if (null !== $query) { + $dup->query = new ParameterBag($query); + } + if (null !== $request) { + $dup->request = new ParameterBag($request); + } + if (null !== $attributes) { + $dup->attributes = new ParameterBag($attributes); + } + if (null !== $cookies) { + $dup->cookies = new ParameterBag($cookies); + } + if (null !== $files) { + $dup->files = new FileBag($files); + } + if (null !== $server) { + $dup->server = new ServerBag($server); + $dup->headers = new HeaderBag($dup->server->getHeaders()); + } + $dup->languages = null; + $dup->charsets = null; + $dup->encodings = null; + $dup->acceptableContentTypes = null; + $dup->pathInfo = null; + $dup->requestUri = null; + $dup->baseUrl = null; + $dup->basePath = null; + $dup->method = null; + $dup->format = null; + + if (!$dup->get('_format') && $this->get('_format')) { + $dup->attributes->set('_format', $this->get('_format')); + } + + if (!$dup->getRequestFormat(null)) { + $dup->setRequestFormat($this->getRequestFormat(null)); + } + + return $dup; + } + + /** + * Clones the current request. + * + * Note that the session is not cloned as duplicated requests + * are most of the time sub-requests of the main one. + */ + public function __clone() + { + $this->query = clone $this->query; + $this->request = clone $this->request; + $this->attributes = clone $this->attributes; + $this->cookies = clone $this->cookies; + $this->files = clone $this->files; + $this->server = clone $this->server; + $this->headers = clone $this->headers; + } + + /** + * Returns the request as a string. + * + * @return string The request + */ + public function __toString() + { + try { + $content = $this->getContent(); + } catch (\LogicException $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + + return trigger_error($e, E_USER_ERROR); + } + + $cookieHeader = ''; + $cookies = []; + + foreach ($this->cookies as $k => $v) { + $cookies[] = $k.'='.$v; + } + + if (!empty($cookies)) { + $cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n"; + } + + return + sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n". + $this->headers. + $cookieHeader."\r\n". + $content; + } + + /** + * Overrides the PHP global variables according to this request instance. + * + * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE. + * $_FILES is never overridden, see rfc1867 + */ + public function overrideGlobals() + { + $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), '', '&'))); + + $_GET = $this->query->all(); + $_POST = $this->request->all(); + $_SERVER = $this->server->all(); + $_COOKIE = $this->cookies->all(); + + foreach ($this->headers->all() as $key => $value) { + $key = strtoupper(str_replace('-', '_', $key)); + if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) { + $_SERVER[$key] = implode(', ', $value); + } else { + $_SERVER['HTTP_'.$key] = implode(', ', $value); + } + } + + $request = ['g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE]; + + $requestOrder = ini_get('request_order') ?: ini_get('variables_order'); + $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp'; + + $_REQUEST = [[]]; + + foreach (str_split($requestOrder) as $order) { + $_REQUEST[] = $request[$order]; + } + + $_REQUEST = array_merge(...$_REQUEST); + } + + /** + * Sets a list of trusted proxies. + * + * You should only list the reverse proxies that you manage directly. + * + * @param array $proxies A list of trusted proxies, the string 'REMOTE_ADDR' will be replaced with $_SERVER['REMOTE_ADDR'] + * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies + * + * @throws \InvalidArgumentException When $trustedHeaderSet is invalid + */ + public static function setTrustedProxies(array $proxies, int $trustedHeaderSet) + { + self::$trustedProxies = array_reduce($proxies, function ($proxies, $proxy) { + if ('REMOTE_ADDR' !== $proxy) { + $proxies[] = $proxy; + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $proxies[] = $_SERVER['REMOTE_ADDR']; + } + + return $proxies; + }, []); + self::$trustedHeaderSet = $trustedHeaderSet; + } + + /** + * Gets the list of trusted proxies. + * + * @return array An array of trusted proxies + */ + public static function getTrustedProxies() + { + return self::$trustedProxies; + } + + /** + * Gets the set of trusted headers from trusted proxies. + * + * @return int A bit field of Request::HEADER_* that defines which headers are trusted from your proxies + */ + public static function getTrustedHeaderSet() + { + return self::$trustedHeaderSet; + } + + /** + * Sets a list of trusted host patterns. + * + * You should only list the hosts you manage using regexs. + * + * @param array $hostPatterns A list of trusted host patterns + */ + public static function setTrustedHosts(array $hostPatterns) + { + self::$trustedHostPatterns = array_map(function ($hostPattern) { + return sprintf('{%s}i', $hostPattern); + }, $hostPatterns); + // we need to reset trusted hosts on trusted host patterns change + self::$trustedHosts = []; + } + + /** + * Gets the list of trusted host patterns. + * + * @return array An array of trusted host patterns + */ + public static function getTrustedHosts() + { + return self::$trustedHostPatterns; + } + + /** + * Normalizes a query string. + * + * It builds a normalized query string, where keys/value pairs are alphabetized, + * have consistent escaping and unneeded delimiters are removed. + * + * @return string A normalized query string for the Request + */ + public static function normalizeQueryString(?string $qs) + { + if ('' === ($qs ?? '')) { + return ''; + } + + parse_str($qs, $qs); + ksort($qs); + + return http_build_query($qs, '', '&', PHP_QUERY_RFC3986); + } + + /** + * Enables support for the _method request parameter to determine the intended HTTP method. + * + * Be warned that enabling this feature might lead to CSRF issues in your code. + * Check that you are using CSRF tokens when required. + * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered + * and used to send a "PUT" or "DELETE" request via the _method request parameter. + * If these methods are not protected against CSRF, this presents a possible vulnerability. + * + * The HTTP method can only be overridden when the real HTTP method is POST. + */ + public static function enableHttpMethodParameterOverride() + { + self::$httpMethodParameterOverride = true; + } + + /** + * Checks whether support for the _method request parameter is enabled. + * + * @return bool True when the _method request parameter is enabled, false otherwise + */ + public static function getHttpMethodParameterOverride() + { + return self::$httpMethodParameterOverride; + } + + /** + * Gets a "parameter" value from any bag. + * + * This method is mainly useful for libraries that want to provide some flexibility. If you don't need the + * flexibility in controllers, it is better to explicitly get request parameters from the appropriate + * public property instead (attributes, query, request). + * + * Order of precedence: PATH (routing placeholders or custom attributes), GET, BODY + * + * @param mixed $default The default value if the parameter key does not exist + * + * @return mixed + */ + public function get(string $key, $default = null) + { + if ($this !== $result = $this->attributes->get($key, $this)) { + return $result; + } + + if ($this !== $result = $this->query->get($key, $this)) { + return $result; + } + + if ($this !== $result = $this->request->get($key, $this)) { + return $result; + } + + return $default; + } + + /** + * Gets the Session. + * + * @return SessionInterface The session + */ + public function getSession() + { + $session = $this->session; + if (!$session instanceof SessionInterface && null !== $session) { + $this->setSession($session = $session()); + } + + if (null === $session) { + throw new \BadMethodCallException('Session has not been set.'); + } + + return $session; + } + + /** + * Whether the request contains a Session which was started in one of the + * previous requests. + * + * @return bool + */ + public function hasPreviousSession() + { + // the check for $this->session avoids malicious users trying to fake a session cookie with proper name + return $this->hasSession() && $this->cookies->has($this->getSession()->getName()); + } + + /** + * Whether the request contains a Session object. + * + * This method does not give any information about the state of the session object, + * like whether the session is started or not. It is just a way to check if this Request + * is associated with a Session instance. + * + * @return bool true when the Request contains a Session object, false otherwise + */ + public function hasSession() + { + return null !== $this->session; + } + + public function setSession(SessionInterface $session) + { + $this->session = $session; + } + + /** + * @internal + */ + public function setSessionFactory(callable $factory) + { + $this->session = $factory; + } + + /** + * Returns the client IP addresses. + * + * In the returned array the most trusted IP address is first, and the + * least trusted one last. The "real" client IP address is the last one, + * but this is also the least trusted one. Trusted proxies are stripped. + * + * Use this method carefully; you should use getClientIp() instead. + * + * @return array The client IP addresses + * + * @see getClientIp() + */ + public function getClientIps() + { + $ip = $this->server->get('REMOTE_ADDR'); + + if (!$this->isFromTrustedProxy()) { + return [$ip]; + } + + return $this->getTrustedValues(self::HEADER_X_FORWARDED_FOR, $ip) ?: [$ip]; + } + + /** + * Returns the client IP address. + * + * This method can read the client IP address from the "X-Forwarded-For" header + * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For" + * header value is a comma+space separated list of IP addresses, the left-most + * being the original client, and each successive proxy that passed the request + * adding the IP address where it received the request from. + * + * If your reverse proxy uses a different header name than "X-Forwarded-For", + * ("Client-Ip" for instance), configure it via the $trustedHeaderSet + * argument of the Request::setTrustedProxies() method instead. + * + * @return string|null The client IP address + * + * @see getClientIps() + * @see https://wikipedia.org/wiki/X-Forwarded-For + */ + public function getClientIp() + { + $ipAddresses = $this->getClientIps(); + + return $ipAddresses[0]; + } + + /** + * Returns current script name. + * + * @return string + */ + public function getScriptName() + { + return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', '')); + } + + /** + * Returns the path being requested relative to the executed script. + * + * The path info always starts with a /. + * + * Suppose this request is instantiated from /mysite on localhost: + * + * * http://localhost/mysite returns an empty string + * * http://localhost/mysite/about returns '/about' + * * http://localhost/mysite/enco%20ded returns '/enco%20ded' + * * http://localhost/mysite/about?var=1 returns '/about' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getPathInfo() + { + if (null === $this->pathInfo) { + $this->pathInfo = $this->preparePathInfo(); + } + + return $this->pathInfo; + } + + /** + * Returns the root path from which this request is executed. + * + * Suppose that an index.php file instantiates this request object: + * + * * http://localhost/index.php returns an empty string + * * http://localhost/index.php/page returns an empty string + * * http://localhost/web/index.php returns '/web' + * * http://localhost/we%20b/index.php returns '/we%20b' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getBasePath() + { + if (null === $this->basePath) { + $this->basePath = $this->prepareBasePath(); + } + + return $this->basePath; + } + + /** + * Returns the root URL from which this request is executed. + * + * The base URL never ends with a /. + * + * This is similar to getBasePath(), except that it also includes the + * script filename (e.g. index.php) if one exists. + * + * @return string The raw URL (i.e. not urldecoded) + */ + public function getBaseUrl() + { + if (null === $this->baseUrl) { + $this->baseUrl = $this->prepareBaseUrl(); + } + + return $this->baseUrl; + } + + /** + * Gets the request's scheme. + * + * @return string + */ + public function getScheme() + { + return $this->isSecure() ? 'https' : 'http'; + } + + /** + * Returns the port on which the request is made. + * + * This method can read the client port from the "X-Forwarded-Port" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Port" header must contain the client port. + * + * @return int|string can be a string if fetched from the server bag + */ + public function getPort() + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_PORT)) { + $host = $host[0]; + } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + return $this->server->get('SERVER_PORT'); + } + + if ('[' === $host[0]) { + $pos = strpos($host, ':', strrpos($host, ']')); + } else { + $pos = strrpos($host, ':'); + } + + if (false !== $pos && $port = substr($host, $pos + 1)) { + return (int) $port; + } + + return 'https' === $this->getScheme() ? 443 : 80; + } + + /** + * Returns the user. + * + * @return string|null + */ + public function getUser() + { + return $this->headers->get('PHP_AUTH_USER'); + } + + /** + * Returns the password. + * + * @return string|null + */ + public function getPassword() + { + return $this->headers->get('PHP_AUTH_PW'); + } + + /** + * Gets the user info. + * + * @return string A user name and, optionally, scheme-specific information about how to gain authorization to access the server + */ + public function getUserInfo() + { + $userinfo = $this->getUser(); + + $pass = $this->getPassword(); + if ('' != $pass) { + $userinfo .= ":$pass"; + } + + return $userinfo; + } + + /** + * Returns the HTTP host being requested. + * + * The port name will be appended to the host if it's non-standard. + * + * @return string + */ + public function getHttpHost() + { + $scheme = $this->getScheme(); + $port = $this->getPort(); + + if (('http' == $scheme && 80 == $port) || ('https' == $scheme && 443 == $port)) { + return $this->getHost(); + } + + return $this->getHost().':'.$port; + } + + /** + * Returns the requested URI (path and query string). + * + * @return string The raw URI (i.e. not URI decoded) + */ + public function getRequestUri() + { + if (null === $this->requestUri) { + $this->requestUri = $this->prepareRequestUri(); + } + + return $this->requestUri; + } + + /** + * Gets the scheme and HTTP host. + * + * If the URL was called with basic authentication, the user + * and the password are not added to the generated string. + * + * @return string The scheme and HTTP host + */ + public function getSchemeAndHttpHost() + { + return $this->getScheme().'://'.$this->getHttpHost(); + } + + /** + * Generates a normalized URI (URL) for the Request. + * + * @return string A normalized URI (URL) for the Request + * + * @see getQueryString() + */ + public function getUri() + { + if (null !== $qs = $this->getQueryString()) { + $qs = '?'.$qs; + } + + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs; + } + + /** + * Generates a normalized URI for the given path. + * + * @param string $path A path to use instead of the current one + * + * @return string The normalized URI for the path + */ + public function getUriForPath(string $path) + { + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path; + } + + /** + * Returns the path as relative reference from the current Request path. + * + * Only the URIs path component (no schema, host etc.) is relevant and must be given. + * Both paths must be absolute and not contain relative parts. + * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. + * Furthermore, they can be used to reduce the link size in documents. + * + * Example target paths, given a base path of "/a/b/c/d": + * - "/a/b/c/d" -> "" + * - "/a/b/c/" -> "./" + * - "/a/b/" -> "../" + * - "/a/b/c/other" -> "other" + * - "/a/x/y" -> "../../x/y" + * + * @return string The relative target path + */ + public function getRelativeUriForPath(string $path) + { + // be sure that we are dealing with an absolute path + if (!isset($path[0]) || '/' !== $path[0]) { + return $path; + } + + if ($path === $basePath = $this->getPathInfo()) { + return ''; + } + + $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); + $targetDirs = explode('/', substr($path, 1)); + array_pop($sourceDirs); + $targetFile = array_pop($targetDirs); + + foreach ($sourceDirs as $i => $dir) { + if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { + unset($sourceDirs[$i], $targetDirs[$i]); + } else { + break; + } + } + + $targetDirs[] = $targetFile; + $path = str_repeat('../', \count($sourceDirs)).implode('/', $targetDirs); + + // A reference to the same base directory or an empty subdirectory must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name + // (see https://tools.ietf.org/html/rfc3986#section-4.2). + return !isset($path[0]) || '/' === $path[0] + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } + + /** + * Generates the normalized query string for the Request. + * + * It builds a normalized query string, where keys/value pairs are alphabetized + * and have consistent escaping. + * + * @return string|null A normalized query string for the Request + */ + public function getQueryString() + { + $qs = static::normalizeQueryString($this->server->get('QUERY_STRING')); + + return '' === $qs ? null : $qs; + } + + /** + * Checks whether the request is secure or not. + * + * This method can read the client protocol from the "X-Forwarded-Proto" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http". + * + * @return bool + */ + public function isSecure() + { + if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_X_FORWARDED_PROTO)) { + return \in_array(strtolower($proto[0]), ['https', 'on', 'ssl', '1'], true); + } + + $https = $this->server->get('HTTPS'); + + return !empty($https) && 'off' !== strtolower($https); + } + + /** + * Returns the host name. + * + * This method can read the client host name from the "X-Forwarded-Host" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Host" header must contain the client host name. + * + * @return string + * + * @throws SuspiciousOperationException when the host name is invalid or not trusted + */ + public function getHost() + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + if (!$host = $this->server->get('SERVER_NAME')) { + $host = $this->server->get('SERVER_ADDR', ''); + } + } + + // trim and remove port number from host + // host is lowercase as per RFC 952/2181 + $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); + + // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user) + // check that it does not contain forbidden characters (see RFC 952 and RFC 2181) + // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names + if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) { + if (!$this->isHostValid) { + return ''; + } + $this->isHostValid = false; + + throw new SuspiciousOperationException(sprintf('Invalid Host "%s".', $host)); + } + + if (\count(self::$trustedHostPatterns) > 0) { + // to avoid host header injection attacks, you should provide a list of trusted host patterns + + if (\in_array($host, self::$trustedHosts)) { + return $host; + } + + foreach (self::$trustedHostPatterns as $pattern) { + if (preg_match($pattern, $host)) { + self::$trustedHosts[] = $host; + + return $host; + } + } + + if (!$this->isHostValid) { + return ''; + } + $this->isHostValid = false; + + throw new SuspiciousOperationException(sprintf('Untrusted Host "%s".', $host)); + } + + return $host; + } + + /** + * Sets the request method. + */ + public function setMethod(string $method) + { + $this->method = null; + $this->server->set('REQUEST_METHOD', $method); + } + + /** + * Gets the request "intended" method. + * + * If the X-HTTP-Method-Override header is set, and if the method is a POST, + * then it is used to determine the "real" intended HTTP method. + * + * The _method request parameter can also be used to determine the HTTP method, + * but only if enableHttpMethodParameterOverride() has been called. + * + * The method is always an uppercased string. + * + * @return string The request method + * + * @see getRealMethod() + */ + public function getMethod() + { + if (null !== $this->method) { + return $this->method; + } + + $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + + if ('POST' !== $this->method) { + return $this->method; + } + + $method = $this->headers->get('X-HTTP-METHOD-OVERRIDE'); + + if (!$method && self::$httpMethodParameterOverride) { + $method = $this->request->get('_method', $this->query->get('_method', 'POST')); + } + + if (!\is_string($method)) { + return $this->method; + } + + $method = strtoupper($method); + + if (\in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'PATCH', 'PURGE', 'TRACE'], true)) { + return $this->method = $method; + } + + if (!preg_match('/^[A-Z]++$/D', $method)) { + throw new SuspiciousOperationException(sprintf('Invalid method override "%s".', $method)); + } + + return $this->method = $method; + } + + /** + * Gets the "real" request method. + * + * @return string The request method + * + * @see getMethod() + */ + public function getRealMethod() + { + return strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + } + + /** + * Gets the mime type associated with the format. + * + * @return string|null The associated mime type (null if not found) + */ + public function getMimeType(string $format) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return isset(static::$formats[$format]) ? static::$formats[$format][0] : null; + } + + /** + * Gets the mime types associated with the format. + * + * @return array The associated mime types + */ + public static function getMimeTypes(string $format) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return isset(static::$formats[$format]) ? static::$formats[$format] : []; + } + + /** + * Gets the format associated with the mime type. + * + * @return string|null The format (null if not found) + */ + public function getFormat(?string $mimeType) + { + $canonicalMimeType = null; + if (false !== $pos = strpos($mimeType, ';')) { + $canonicalMimeType = trim(substr($mimeType, 0, $pos)); + } + + if (null === static::$formats) { + static::initializeFormats(); + } + + foreach (static::$formats as $format => $mimeTypes) { + if (\in_array($mimeType, (array) $mimeTypes)) { + return $format; + } + if (null !== $canonicalMimeType && \in_array($canonicalMimeType, (array) $mimeTypes)) { + return $format; + } + } + + return null; + } + + /** + * Associates a format with mime types. + * + * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) + */ + public function setFormat(?string $format, $mimeTypes) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + static::$formats[$format] = \is_array($mimeTypes) ? $mimeTypes : [$mimeTypes]; + } + + /** + * Gets the request format. + * + * Here is the process to determine the format: + * + * * format defined by the user (with setRequestFormat()) + * * _format request attribute + * * $default + * + * @see getPreferredFormat + * + * @return string|null The request format + */ + public function getRequestFormat(?string $default = 'html') + { + if (null === $this->format) { + $this->format = $this->attributes->get('_format'); + } + + return null === $this->format ? $default : $this->format; + } + + /** + * Sets the request format. + */ + public function setRequestFormat(?string $format) + { + $this->format = $format; + } + + /** + * Gets the format associated with the request. + * + * @return string|null The format (null if no content type is present) + */ + public function getContentType() + { + return $this->getFormat($this->headers->get('CONTENT_TYPE')); + } + + /** + * Sets the default locale. + */ + public function setDefaultLocale(string $locale) + { + $this->defaultLocale = $locale; + + if (null === $this->locale) { + $this->setPhpDefaultLocale($locale); + } + } + + /** + * Get the default locale. + * + * @return string + */ + public function getDefaultLocale() + { + return $this->defaultLocale; + } + + /** + * Sets the locale. + */ + public function setLocale(string $locale) + { + $this->setPhpDefaultLocale($this->locale = $locale); + } + + /** + * Get the locale. + * + * @return string + */ + public function getLocale() + { + return null === $this->locale ? $this->defaultLocale : $this->locale; + } + + /** + * Checks if the request method is of specified type. + * + * @param string $method Uppercase request method (GET, POST etc) + * + * @return bool + */ + public function isMethod(string $method) + { + return $this->getMethod() === strtoupper($method); + } + + /** + * Checks whether or not the method is safe. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.1 + * + * @return bool + */ + public function isMethodSafe() + { + return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE']); + } + + /** + * Checks whether or not the method is idempotent. + * + * @return bool + */ + public function isMethodIdempotent() + { + return \in_array($this->getMethod(), ['HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE']); + } + + /** + * Checks whether the method is cacheable or not. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.3 + * + * @return bool True for GET and HEAD, false otherwise + */ + public function isMethodCacheable() + { + return \in_array($this->getMethod(), ['GET', 'HEAD']); + } + + /** + * Returns the protocol version. + * + * If the application is behind a proxy, the protocol version used in the + * requests between the client and the proxy and between the proxy and the + * server might be different. This returns the former (from the "Via" header) + * if the proxy is trusted (see "setTrustedProxies()"), otherwise it returns + * the latter (from the "SERVER_PROTOCOL" server parameter). + * + * @return string + */ + public function getProtocolVersion() + { + if ($this->isFromTrustedProxy()) { + preg_match('~^(HTTP/)?([1-9]\.[0-9]) ~', $this->headers->get('Via'), $matches); + + if ($matches) { + return 'HTTP/'.$matches[2]; + } + } + + return $this->server->get('SERVER_PROTOCOL'); + } + + /** + * Returns the request body content. + * + * @param bool $asResource If true, a resource will be returned + * + * @return string|resource The request body content or a resource to read the body stream + * + * @throws \LogicException + */ + public function getContent(bool $asResource = false) + { + $currentContentIsResource = \is_resource($this->content); + + if (true === $asResource) { + if ($currentContentIsResource) { + rewind($this->content); + + return $this->content; + } + + // Content passed in parameter (test) + if (\is_string($this->content)) { + $resource = fopen('php://temp', 'r+'); + fwrite($resource, $this->content); + rewind($resource); + + return $resource; + } + + $this->content = false; + + return fopen('php://input', 'rb'); + } + + if ($currentContentIsResource) { + rewind($this->content); + + return stream_get_contents($this->content); + } + + if (null === $this->content || false === $this->content) { + $this->content = file_get_contents('php://input'); + } + + return $this->content; + } + + /** + * Gets the Etags. + * + * @return array The entity tags + */ + public function getETags() + { + return preg_split('/\s*,\s*/', $this->headers->get('if_none_match'), null, PREG_SPLIT_NO_EMPTY); + } + + /** + * @return bool + */ + public function isNoCache() + { + return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); + } + + /** + * Gets the preferred format for the response by inspecting, in the following order: + * * the request format set using setRequestFormat + * * the values of the Accept HTTP header + * * the content type of the body of the request. + */ + public function getPreferredFormat(?string $default = 'html'): ?string + { + if (null !== $this->preferredFormat) { + return $this->preferredFormat; + } + + $preferredFormat = null; + foreach ($this->getAcceptableContentTypes() as $contentType) { + if ($preferredFormat = $this->getFormat($contentType)) { + break; + } + } + + $this->preferredFormat = $this->getRequestFormat($preferredFormat ?: $this->getContentType()); + + return $this->preferredFormat ?: $default; + } + + /** + * Returns the preferred language. + * + * @param string[] $locales An array of ordered available locales + * + * @return string|null The preferred locale + */ + public function getPreferredLanguage(array $locales = null) + { + $preferredLanguages = $this->getLanguages(); + + if (empty($locales)) { + return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null; + } + + if (!$preferredLanguages) { + return $locales[0]; + } + + $extendedPreferredLanguages = []; + foreach ($preferredLanguages as $language) { + $extendedPreferredLanguages[] = $language; + if (false !== $position = strpos($language, '_')) { + $superLanguage = substr($language, 0, $position); + if (!\in_array($superLanguage, $preferredLanguages)) { + $extendedPreferredLanguages[] = $superLanguage; + } + } + } + + $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales)); + + return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0]; + } + + /** + * Gets a list of languages acceptable by the client browser. + * + * @return array Languages ordered in the user browser preferences + */ + public function getLanguages() + { + if (null !== $this->languages) { + return $this->languages; + } + + $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all(); + $this->languages = []; + foreach ($languages as $lang => $acceptHeaderItem) { + if (false !== strpos($lang, '-')) { + $codes = explode('-', $lang); + if ('i' === $codes[0]) { + // Language not listed in ISO 639 that are not variants + // of any listed language, which can be registered with the + // i-prefix, such as i-cherokee + if (\count($codes) > 1) { + $lang = $codes[1]; + } + } else { + for ($i = 0, $max = \count($codes); $i < $max; ++$i) { + if (0 === $i) { + $lang = strtolower($codes[0]); + } else { + $lang .= '_'.strtoupper($codes[$i]); + } + } + } + } + + $this->languages[] = $lang; + } + + return $this->languages; + } + + /** + * Gets a list of charsets acceptable by the client browser. + * + * @return array List of charsets in preferable order + */ + public function getCharsets() + { + if (null !== $this->charsets) { + return $this->charsets; + } + + return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all()); + } + + /** + * Gets a list of encodings acceptable by the client browser. + * + * @return array List of encodings in preferable order + */ + public function getEncodings() + { + if (null !== $this->encodings) { + return $this->encodings; + } + + return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all()); + } + + /** + * Gets a list of content types acceptable by the client browser. + * + * @return array List of content types in preferable order + */ + public function getAcceptableContentTypes() + { + if (null !== $this->acceptableContentTypes) { + return $this->acceptableContentTypes; + } + + return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all()); + } + + /** + * Returns true if the request is a XMLHttpRequest. + * + * It works if your JavaScript library sets an X-Requested-With HTTP header. + * It is known to work with common JavaScript frameworks: + * + * @see https://wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript + * + * @return bool true if the request is an XMLHttpRequest, false otherwise + */ + public function isXmlHttpRequest() + { + return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); + } + + /* + * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24) + * + * Code subject to the new BSD license (https://framework.zend.com/license). + * + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (https://www.zend.com/) + */ + + protected function prepareRequestUri() + { + $requestUri = ''; + + if ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) { + // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem) + $requestUri = $this->server->get('UNENCODED_URL'); + $this->server->remove('UNENCODED_URL'); + $this->server->remove('IIS_WasUrlRewritten'); + } elseif ($this->server->has('REQUEST_URI')) { + $requestUri = $this->server->get('REQUEST_URI'); + + if ('' !== $requestUri && '/' === $requestUri[0]) { + // To only use path and query remove the fragment. + if (false !== $pos = strpos($requestUri, '#')) { + $requestUri = substr($requestUri, 0, $pos); + } + } else { + // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, + // only use URL path. + $uriComponents = parse_url($requestUri); + + if (isset($uriComponents['path'])) { + $requestUri = $uriComponents['path']; + } + + if (isset($uriComponents['query'])) { + $requestUri .= '?'.$uriComponents['query']; + } + } + } elseif ($this->server->has('ORIG_PATH_INFO')) { + // IIS 5.0, PHP as CGI + $requestUri = $this->server->get('ORIG_PATH_INFO'); + if ('' != $this->server->get('QUERY_STRING')) { + $requestUri .= '?'.$this->server->get('QUERY_STRING'); + } + $this->server->remove('ORIG_PATH_INFO'); + } + + // normalize the request URI to ease creating sub-requests from this request + $this->server->set('REQUEST_URI', $requestUri); + + return $requestUri; + } + + /** + * Prepares the base URL. + * + * @return string + */ + protected function prepareBaseUrl() + { + $filename = basename($this->server->get('SCRIPT_FILENAME')); + + if (basename($this->server->get('SCRIPT_NAME')) === $filename) { + $baseUrl = $this->server->get('SCRIPT_NAME'); + } elseif (basename($this->server->get('PHP_SELF')) === $filename) { + $baseUrl = $this->server->get('PHP_SELF'); + } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) { + $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility + } else { + // Backtrack up the script_filename to find the portion matching + // php_self + $path = $this->server->get('PHP_SELF', ''); + $file = $this->server->get('SCRIPT_FILENAME', ''); + $segs = explode('/', trim($file, '/')); + $segs = array_reverse($segs); + $index = 0; + $last = \count($segs); + $baseUrl = ''; + do { + $seg = $segs[$index]; + $baseUrl = '/'.$seg.$baseUrl; + ++$index; + } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos); + } + + // Does the baseUrl have anything in common with the request_uri? + $requestUri = $this->getRequestUri(); + if ('' !== $requestUri && '/' !== $requestUri[0]) { + $requestUri = '/'.$requestUri; + } + + if ($baseUrl && null !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) { + // full $baseUrl matches + return $prefix; + } + + if ($baseUrl && null !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(\dirname($baseUrl), '/'.\DIRECTORY_SEPARATOR).'/')) { + // directory portion of $baseUrl matches + return rtrim($prefix, '/'.\DIRECTORY_SEPARATOR); + } + + $truncatedRequestUri = $requestUri; + if (false !== $pos = strpos($requestUri, '?')) { + $truncatedRequestUri = substr($requestUri, 0, $pos); + } + + $basename = basename($baseUrl); + if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) { + // no match whatsoever; set it blank + return ''; + } + + // If using mod_rewrite or ISAPI_Rewrite strip the script filename + // out of baseUrl. $pos !== 0 makes sure it is not matching a value + // from PATH_INFO or QUERY_STRING + if (\strlen($requestUri) >= \strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && 0 !== $pos) { + $baseUrl = substr($requestUri, 0, $pos + \strlen($baseUrl)); + } + + return rtrim($baseUrl, '/'.\DIRECTORY_SEPARATOR); + } + + /** + * Prepares the base path. + * + * @return string base path + */ + protected function prepareBasePath() + { + $baseUrl = $this->getBaseUrl(); + if (empty($baseUrl)) { + return ''; + } + + $filename = basename($this->server->get('SCRIPT_FILENAME')); + if (basename($baseUrl) === $filename) { + $basePath = \dirname($baseUrl); + } else { + $basePath = $baseUrl; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $basePath = str_replace('\\', '/', $basePath); + } + + return rtrim($basePath, '/'); + } + + /** + * Prepares the path info. + * + * @return string path info + */ + protected function preparePathInfo() + { + if (null === ($requestUri = $this->getRequestUri())) { + return '/'; + } + + // Remove the query string from REQUEST_URI + if (false !== $pos = strpos($requestUri, '?')) { + $requestUri = substr($requestUri, 0, $pos); + } + if ('' !== $requestUri && '/' !== $requestUri[0]) { + $requestUri = '/'.$requestUri; + } + + if (null === ($baseUrl = $this->getBaseUrl())) { + return $requestUri; + } + + $pathInfo = substr($requestUri, \strlen($baseUrl)); + if (false === $pathInfo || '' === $pathInfo) { + // If substr() returns false then PATH_INFO is set to an empty string + return '/'; + } + + return (string) $pathInfo; + } + + /** + * Initializes HTTP request formats. + */ + protected static function initializeFormats() + { + static::$formats = [ + 'html' => ['text/html', 'application/xhtml+xml'], + 'txt' => ['text/plain'], + 'js' => ['application/javascript', 'application/x-javascript', 'text/javascript'], + 'css' => ['text/css'], + 'json' => ['application/json', 'application/x-json'], + 'jsonld' => ['application/ld+json'], + 'xml' => ['text/xml', 'application/xml', 'application/x-xml'], + 'rdf' => ['application/rdf+xml'], + 'atom' => ['application/atom+xml'], + 'rss' => ['application/rss+xml'], + 'form' => ['application/x-www-form-urlencoded'], + ]; + } + + private function setPhpDefaultLocale(string $locale): void + { + // if either the class Locale doesn't exist, or an exception is thrown when + // setting the default locale, the intl module is not installed, and + // the call can be ignored: + try { + if (class_exists('Locale', false)) { + \Locale::setDefault($locale); + } + } catch (\Exception $e) { + } + } + + /** + * Returns the prefix as encoded in the string when the string starts with + * the given prefix, null otherwise. + */ + private function getUrlencodedPrefix(string $string, string $prefix): ?string + { + if (0 !== strpos(rawurldecode($string), $prefix)) { + return null; + } + + $len = \strlen($prefix); + + if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) { + return $match[0]; + } + + return null; + } + + private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): self + { + if (self::$requestFactory) { + $request = (self::$requestFactory)($query, $request, $attributes, $cookies, $files, $server, $content); + + if (!$request instanceof self) { + throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.'); + } + + return $request; + } + + return new static($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Indicates whether this request originated from a trusted proxy. + * + * This can be useful to determine whether or not to trust the + * contents of a proxy-specific header. + * + * @return bool true if the request came from a trusted proxy, false otherwise + */ + public function isFromTrustedProxy() + { + return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies); + } + + private function getTrustedValues(int $type, string $ip = null): array + { + $clientValues = []; + $forwardedValues = []; + + if ((self::$trustedHeaderSet & $type) && $this->headers->has(self::$trustedHeaders[$type])) { + foreach (explode(',', $this->headers->get(self::$trustedHeaders[$type])) as $v) { + $clientValues[] = (self::HEADER_X_FORWARDED_PORT === $type ? '0.0.0.0:' : '').trim($v); + } + } + + if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) { + $forwarded = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]); + $parts = HeaderUtils::split($forwarded, ',;='); + $forwardedValues = []; + $param = self::$forwardedParams[$type]; + foreach ($parts as $subParts) { + if (null === $v = HeaderUtils::combine($subParts)[$param] ?? null) { + continue; + } + if (self::HEADER_X_FORWARDED_PORT === $type) { + if (']' === substr($v, -1) || false === $v = strrchr($v, ':')) { + $v = $this->isSecure() ? ':443' : ':80'; + } + $v = '0.0.0.0'.$v; + } + $forwardedValues[] = $v; + } + } + + if (null !== $ip) { + $clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip); + $forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip); + } + + if ($forwardedValues === $clientValues || !$clientValues) { + return $forwardedValues; + } + + if (!$forwardedValues) { + return $clientValues; + } + + if (!$this->isForwardedValid) { + return null !== $ip ? ['0.0.0.0', $ip] : []; + } + $this->isForwardedValid = false; + + throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::$trustedHeaders[self::HEADER_FORWARDED], self::$trustedHeaders[$type])); + } + + private function normalizeAndFilterClientIps(array $clientIps, string $ip): array + { + if (!$clientIps) { + return []; + } + $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from + $firstTrustedIp = null; + + foreach ($clientIps as $key => $clientIp) { + if (strpos($clientIp, '.')) { + // Strip :port from IPv4 addresses. This is allowed in Forwarded + // and may occur in X-Forwarded-For. + $i = strpos($clientIp, ':'); + if ($i) { + $clientIps[$key] = $clientIp = substr($clientIp, 0, $i); + } + } elseif (0 === strpos($clientIp, '[')) { + // Strip brackets and :port from IPv6 addresses. + $i = strpos($clientIp, ']', 1); + $clientIps[$key] = $clientIp = substr($clientIp, 1, $i - 1); + } + + if (!filter_var($clientIp, FILTER_VALIDATE_IP)) { + unset($clientIps[$key]); + + continue; + } + + if (IpUtils::checkIp($clientIp, self::$trustedProxies)) { + unset($clientIps[$key]); + + // Fallback to this when the client IP falls into the range of trusted proxies + if (null === $firstTrustedIp) { + $firstTrustedIp = $clientIp; + } + } + } + + // Now the IP chain contains only untrusted proxies and the client IP + return $clientIps ? array_reverse($clientIps) : [$firstTrustedIp]; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/RequestMatcher.php b/lam/lib/3rdParty/composer/symfony/http-foundation/RequestMatcher.php new file mode 100644 index 00000000..c32c5cdc --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/RequestMatcher.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RequestMatcher compares a pre-defined set of checks against a Request instance. + * + * @author Fabien Potencier + */ +class RequestMatcher implements RequestMatcherInterface +{ + /** + * @var string|null + */ + private $path; + + /** + * @var string|null + */ + private $host; + + /** + * @var int|null + */ + private $port; + + /** + * @var string[] + */ + private $methods = []; + + /** + * @var string[] + */ + private $ips = []; + + /** + * @var array + */ + private $attributes = []; + + /** + * @var string[] + */ + private $schemes = []; + + /** + * @param string|string[]|null $methods + * @param string|string[]|null $ips + * @param string|string[]|null $schemes + */ + public function __construct(string $path = null, string $host = null, $methods = null, $ips = null, array $attributes = [], $schemes = null, int $port = null) + { + $this->matchPath($path); + $this->matchHost($host); + $this->matchMethod($methods); + $this->matchIps($ips); + $this->matchScheme($schemes); + $this->matchPort($port); + + foreach ($attributes as $k => $v) { + $this->matchAttribute($k, $v); + } + } + + /** + * Adds a check for the HTTP scheme. + * + * @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes + */ + public function matchScheme($scheme) + { + $this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : []; + } + + /** + * Adds a check for the URL host name. + */ + public function matchHost(?string $regexp) + { + $this->host = $regexp; + } + + /** + * Adds a check for the the URL port. + * + * @param int|null $port The port number to connect to + */ + public function matchPort(?int $port) + { + $this->port = $port; + } + + /** + * Adds a check for the URL path info. + */ + public function matchPath(?string $regexp) + { + $this->path = $regexp; + } + + /** + * Adds a check for the client IP. + * + * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + */ + public function matchIp(string $ip) + { + $this->matchIps($ip); + } + + /** + * Adds a check for the client IP. + * + * @param string|string[]|null $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + */ + public function matchIps($ips) + { + $this->ips = null !== $ips ? (array) $ips : []; + } + + /** + * Adds a check for the HTTP method. + * + * @param string|string[]|null $method An HTTP method or an array of HTTP methods + */ + public function matchMethod($method) + { + $this->methods = null !== $method ? array_map('strtoupper', (array) $method) : []; + } + + /** + * Adds a check for request attribute. + */ + public function matchAttribute(string $key, string $regexp) + { + $this->attributes[$key] = $regexp; + } + + /** + * {@inheritdoc} + */ + public function matches(Request $request) + { + if ($this->schemes && !\in_array($request->getScheme(), $this->schemes, true)) { + return false; + } + + if ($this->methods && !\in_array($request->getMethod(), $this->methods, true)) { + return false; + } + + foreach ($this->attributes as $key => $pattern) { + if (!preg_match('{'.$pattern.'}', $request->attributes->get($key))) { + return false; + } + } + + if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getPathInfo()))) { + return false; + } + + if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getHost())) { + return false; + } + + if (null !== $this->port && 0 < $this->port && $request->getPort() !== $this->port) { + return false; + } + + if (IpUtils::checkIp($request->getClientIp(), $this->ips)) { + return true; + } + + // Note to future implementors: add additional checks above the + // foreach above or else your check might not be run! + return 0 === \count($this->ips); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/RequestMatcherInterface.php b/lam/lib/3rdParty/composer/symfony/http-foundation/RequestMatcherInterface.php new file mode 100644 index 00000000..c26db3e6 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/RequestMatcherInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RequestMatcherInterface is an interface for strategies to match a Request. + * + * @author Fabien Potencier + */ +interface RequestMatcherInterface +{ + /** + * Decides whether the rule(s) implemented by the strategy matches the supplied request. + * + * @return bool true if the request matches, false otherwise + */ + public function matches(Request $request); +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/RequestStack.php b/lam/lib/3rdParty/composer/symfony/http-foundation/RequestStack.php new file mode 100644 index 00000000..244a77d6 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/RequestStack.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Request stack that controls the lifecycle of requests. + * + * @author Benjamin Eberlei + */ +class RequestStack +{ + /** + * @var Request[] + */ + private $requests = []; + + /** + * Pushes a Request on the stack. + * + * This method should generally not be called directly as the stack + * management should be taken care of by the application itself. + */ + public function push(Request $request) + { + $this->requests[] = $request; + } + + /** + * Pops the current request from the stack. + * + * This operation lets the current request go out of scope. + * + * This method should generally not be called directly as the stack + * management should be taken care of by the application itself. + * + * @return Request|null + */ + public function pop() + { + if (!$this->requests) { + return null; + } + + return array_pop($this->requests); + } + + /** + * @return Request|null + */ + public function getCurrentRequest() + { + return end($this->requests) ?: null; + } + + /** + * Gets the master Request. + * + * Be warned that making your code aware of the master request + * might make it un-compatible with other features of your framework + * like ESI support. + * + * @return Request|null + */ + public function getMasterRequest() + { + if (!$this->requests) { + return null; + } + + return $this->requests[0]; + } + + /** + * Returns the parent request of the current. + * + * Be warned that making your code aware of the parent request + * might make it un-compatible with other features of your framework + * like ESI support. + * + * If current Request is the master request, it returns null. + * + * @return Request|null + */ + public function getParentRequest() + { + $pos = \count($this->requests) - 2; + + if (!isset($this->requests[$pos])) { + return null; + } + + return $this->requests[$pos]; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Response.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Response.php new file mode 100644 index 00000000..9122b13c --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Response.php @@ -0,0 +1,1224 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Response represents an HTTP response. + * + * @author Fabien Potencier + */ +class Response +{ + const HTTP_CONTINUE = 100; + const HTTP_SWITCHING_PROTOCOLS = 101; + const HTTP_PROCESSING = 102; // RFC2518 + const HTTP_EARLY_HINTS = 103; // RFC8297 + const HTTP_OK = 200; + const HTTP_CREATED = 201; + const HTTP_ACCEPTED = 202; + const HTTP_NON_AUTHORITATIVE_INFORMATION = 203; + const HTTP_NO_CONTENT = 204; + const HTTP_RESET_CONTENT = 205; + const HTTP_PARTIAL_CONTENT = 206; + const HTTP_MULTI_STATUS = 207; // RFC4918 + const HTTP_ALREADY_REPORTED = 208; // RFC5842 + const HTTP_IM_USED = 226; // RFC3229 + const HTTP_MULTIPLE_CHOICES = 300; + const HTTP_MOVED_PERMANENTLY = 301; + const HTTP_FOUND = 302; + const HTTP_SEE_OTHER = 303; + const HTTP_NOT_MODIFIED = 304; + const HTTP_USE_PROXY = 305; + const HTTP_RESERVED = 306; + const HTTP_TEMPORARY_REDIRECT = 307; + const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238 + const HTTP_BAD_REQUEST = 400; + const HTTP_UNAUTHORIZED = 401; + const HTTP_PAYMENT_REQUIRED = 402; + const HTTP_FORBIDDEN = 403; + const HTTP_NOT_FOUND = 404; + const HTTP_METHOD_NOT_ALLOWED = 405; + const HTTP_NOT_ACCEPTABLE = 406; + const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; + const HTTP_REQUEST_TIMEOUT = 408; + const HTTP_CONFLICT = 409; + const HTTP_GONE = 410; + const HTTP_LENGTH_REQUIRED = 411; + const HTTP_PRECONDITION_FAILED = 412; + const HTTP_REQUEST_ENTITY_TOO_LARGE = 413; + const HTTP_REQUEST_URI_TOO_LONG = 414; + const HTTP_UNSUPPORTED_MEDIA_TYPE = 415; + const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; + const HTTP_EXPECTATION_FAILED = 417; + const HTTP_I_AM_A_TEAPOT = 418; // RFC2324 + const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540 + const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918 + const HTTP_LOCKED = 423; // RFC4918 + const HTTP_FAILED_DEPENDENCY = 424; // RFC4918 + const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04 + const HTTP_UPGRADE_REQUIRED = 426; // RFC2817 + const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585 + const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585 + const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585 + const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; + const HTTP_INTERNAL_SERVER_ERROR = 500; + const HTTP_NOT_IMPLEMENTED = 501; + const HTTP_BAD_GATEWAY = 502; + const HTTP_SERVICE_UNAVAILABLE = 503; + const HTTP_GATEWAY_TIMEOUT = 504; + const HTTP_VERSION_NOT_SUPPORTED = 505; + const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295 + const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918 + const HTTP_LOOP_DETECTED = 508; // RFC5842 + const HTTP_NOT_EXTENDED = 510; // RFC2774 + const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585 + + /** + * @var ResponseHeaderBag + */ + public $headers; + + /** + * @var string + */ + protected $content; + + /** + * @var string + */ + protected $version; + + /** + * @var int + */ + protected $statusCode; + + /** + * @var string + */ + protected $statusText; + + /** + * @var string + */ + protected $charset; + + /** + * Status codes translation table. + * + * The list of codes is complete according to the + * {@link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml Hypertext Transfer Protocol (HTTP) Status Code Registry} + * (last updated 2016-03-01). + * + * Unless otherwise noted, the status code is defined in RFC2616. + * + * @var array + */ + public static $statusTexts = [ + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', // RFC2518 + 103 => 'Early Hints', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', // RFC4918 + 208 => 'Already Reported', // RFC5842 + 226 => 'IM Used', // RFC3229 + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', // RFC7238 + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Payload Too Large', + 414 => 'URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', // RFC2324 + 421 => 'Misdirected Request', // RFC7540 + 422 => 'Unprocessable Entity', // RFC4918 + 423 => 'Locked', // RFC4918 + 424 => 'Failed Dependency', // RFC4918 + 425 => 'Too Early', // RFC-ietf-httpbis-replay-04 + 426 => 'Upgrade Required', // RFC2817 + 428 => 'Precondition Required', // RFC6585 + 429 => 'Too Many Requests', // RFC6585 + 431 => 'Request Header Fields Too Large', // RFC6585 + 451 => 'Unavailable For Legal Reasons', // RFC7725 + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', // RFC2295 + 507 => 'Insufficient Storage', // RFC4918 + 508 => 'Loop Detected', // RFC5842 + 510 => 'Not Extended', // RFC2774 + 511 => 'Network Authentication Required', // RFC6585 + ]; + + /** + * @throws \InvalidArgumentException When the HTTP status code is not valid + */ + public function __construct(?string $content = '', int $status = 200, array $headers = []) + { + $this->headers = new ResponseHeaderBag($headers); + $this->setContent($content); + $this->setStatusCode($status); + $this->setProtocolVersion('1.0'); + } + + /** + * Factory method for chainability. + * + * Example: + * + * return Response::create($body, 200) + * ->setSharedMaxAge(300); + * + * @return static + */ + public static function create(?string $content = '', int $status = 200, array $headers = []) + { + return new static($content, $status, $headers); + } + + /** + * Returns the Response as an HTTP string. + * + * The string representation of the Response is the same as the + * one that will be sent to the client only if the prepare() method + * has been called before. + * + * @return string The Response as an HTTP string + * + * @see prepare() + */ + public function __toString() + { + return + sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n". + $this->headers."\r\n". + $this->getContent(); + } + + /** + * Clones the current Response instance. + */ + public function __clone() + { + $this->headers = clone $this->headers; + } + + /** + * Prepares the Response before it is sent to the client. + * + * This method tweaks the Response to ensure that it is + * compliant with RFC 2616. Most of the changes are based on + * the Request that is "associated" with this Response. + * + * @return $this + */ + public function prepare(Request $request) + { + $headers = $this->headers; + + if ($this->isInformational() || $this->isEmpty()) { + $this->setContent(null); + $headers->remove('Content-Type'); + $headers->remove('Content-Length'); + } else { + // Content-type based on the Request + if (!$headers->has('Content-Type')) { + $format = $request->getPreferredFormat(); + if (null !== $format && $mimeType = $request->getMimeType($format)) { + $headers->set('Content-Type', $mimeType); + } + } + + // Fix Content-Type + $charset = $this->charset ?: 'UTF-8'; + if (!$headers->has('Content-Type')) { + $headers->set('Content-Type', 'text/html; charset='.$charset); + } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) { + // add the charset + $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset); + } + + // Fix Content-Length + if ($headers->has('Transfer-Encoding')) { + $headers->remove('Content-Length'); + } + + if ($request->isMethod('HEAD')) { + // cf. RFC2616 14.13 + $length = $headers->get('Content-Length'); + $this->setContent(null); + if ($length) { + $headers->set('Content-Length', $length); + } + } + } + + // Fix protocol + if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + // Check if we need to send extra expire info headers + if ('1.0' == $this->getProtocolVersion() && false !== strpos($headers->get('Cache-Control'), 'no-cache')) { + $headers->set('pragma', 'no-cache'); + $headers->set('expires', -1); + } + + $this->ensureIEOverSSLCompatibility($request); + + if ($request->isSecure()) { + foreach ($headers->getCookies() as $cookie) { + $cookie->setSecureDefault(true); + } + } + + return $this; + } + + /** + * Sends HTTP headers. + * + * @return $this + */ + public function sendHeaders() + { + // headers have already been sent by the developer + if (headers_sent()) { + return $this; + } + + // headers + foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) { + $replace = 0 === strcasecmp($name, 'Content-Type'); + foreach ($values as $value) { + header($name.': '.$value, $replace, $this->statusCode); + } + } + + // cookies + foreach ($this->headers->getCookies() as $cookie) { + header('Set-Cookie: '.$cookie, false, $this->statusCode); + } + + // status + header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); + + return $this; + } + + /** + * Sends content for the current web response. + * + * @return $this + */ + public function sendContent() + { + echo $this->content; + + return $this; + } + + /** + * Sends HTTP headers and content. + * + * @return $this + */ + public function send() + { + $this->sendHeaders(); + $this->sendContent(); + + if (\function_exists('fastcgi_finish_request')) { + fastcgi_finish_request(); + } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { + static::closeOutputBuffers(0, true); + } + + return $this; + } + + /** + * Sets the response content. + * + * @return $this + * + * @throws \UnexpectedValueException + */ + public function setContent(?string $content) + { + $this->content = $content ?? ''; + + return $this; + } + + /** + * Gets the current response content. + * + * @return string|false + */ + public function getContent() + { + return $this->content; + } + + /** + * Sets the HTTP protocol version (1.0 or 1.1). + * + * @return $this + * + * @final + */ + public function setProtocolVersion(string $version): object + { + $this->version = $version; + + return $this; + } + + /** + * Gets the HTTP protocol version. + * + * @final + */ + public function getProtocolVersion(): string + { + return $this->version; + } + + /** + * Sets the response status code. + * + * If the status text is null it will be automatically populated for the known + * status codes and left empty otherwise. + * + * @return $this + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + * + * @final + */ + public function setStatusCode(int $code, $text = null): object + { + $this->statusCode = $code; + if ($this->isInvalid()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); + } + + if (null === $text) { + $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status'; + + return $this; + } + + if (false === $text) { + $this->statusText = ''; + + return $this; + } + + $this->statusText = $text; + + return $this; + } + + /** + * Retrieves the status code for the current web response. + * + * @final + */ + public function getStatusCode(): int + { + return $this->statusCode; + } + + /** + * Sets the response charset. + * + * @return $this + * + * @final + */ + public function setCharset(string $charset): object + { + $this->charset = $charset; + + return $this; + } + + /** + * Retrieves the response charset. + * + * @final + */ + public function getCharset(): ?string + { + return $this->charset; + } + + /** + * Returns true if the response may safely be kept in a shared (surrogate) cache. + * + * Responses marked "private" with an explicit Cache-Control directive are + * considered uncacheable. + * + * Responses with neither a freshness lifetime (Expires, max-age) nor cache + * validator (Last-Modified, ETag) are considered uncacheable because there is + * no way to tell when or how to remove them from the cache. + * + * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation, + * for example "status codes that are defined as cacheable by default [...] + * can be reused by a cache with heuristic expiration unless otherwise indicated" + * (https://tools.ietf.org/html/rfc7231#section-6.1) + * + * @final + */ + public function isCacheable(): bool + { + if (!\in_array($this->statusCode, [200, 203, 300, 301, 302, 404, 410])) { + return false; + } + + if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) { + return false; + } + + return $this->isValidateable() || $this->isFresh(); + } + + /** + * Returns true if the response is "fresh". + * + * Fresh responses may be served from cache without any interaction with the + * origin. A response is considered fresh when it includes a Cache-Control/max-age + * indicator or Expires header and the calculated age is less than the freshness lifetime. + * + * @final + */ + public function isFresh(): bool + { + return $this->getTtl() > 0; + } + + /** + * Returns true if the response includes headers that can be used to validate + * the response with the origin server using a conditional GET request. + * + * @final + */ + public function isValidateable(): bool + { + return $this->headers->has('Last-Modified') || $this->headers->has('ETag'); + } + + /** + * Marks the response as "private". + * + * It makes the response ineligible for serving other clients. + * + * @return $this + * + * @final + */ + public function setPrivate(): object + { + $this->headers->removeCacheControlDirective('public'); + $this->headers->addCacheControlDirective('private'); + + return $this; + } + + /** + * Marks the response as "public". + * + * It makes the response eligible for serving other clients. + * + * @return $this + * + * @final + */ + public function setPublic(): object + { + $this->headers->addCacheControlDirective('public'); + $this->headers->removeCacheControlDirective('private'); + + return $this; + } + + /** + * Marks the response as "immutable". + * + * @return $this + * + * @final + */ + public function setImmutable(bool $immutable = true): object + { + if ($immutable) { + $this->headers->addCacheControlDirective('immutable'); + } else { + $this->headers->removeCacheControlDirective('immutable'); + } + + return $this; + } + + /** + * Returns true if the response is marked as "immutable". + * + * @final + */ + public function isImmutable(): bool + { + return $this->headers->hasCacheControlDirective('immutable'); + } + + /** + * Returns true if the response must be revalidated by caches. + * + * This method indicates that the response must not be served stale by a + * cache in any circumstance without first revalidating with the origin. + * When present, the TTL of the response should not be overridden to be + * greater than the value provided by the origin. + * + * @final + */ + public function mustRevalidate(): bool + { + return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate'); + } + + /** + * Returns the Date header as a DateTime instance. + * + * @throws \RuntimeException When the header is not parseable + * + * @final + */ + public function getDate(): ?\DateTimeInterface + { + return $this->headers->getDate('Date'); + } + + /** + * Sets the Date header. + * + * @return $this + * + * @final + */ + public function setDate(\DateTimeInterface $date): object + { + if ($date instanceof \DateTime) { + $date = \DateTimeImmutable::createFromMutable($date); + } + + $date = $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the age of the response in seconds. + * + * @final + */ + public function getAge(): int + { + if (null !== $age = $this->headers->get('Age')) { + return (int) $age; + } + + return max(time() - (int) $this->getDate()->format('U'), 0); + } + + /** + * Marks the response stale by setting the Age header to be equal to the maximum age of the response. + * + * @return $this + */ + public function expire() + { + if ($this->isFresh()) { + $this->headers->set('Age', $this->getMaxAge()); + $this->headers->remove('Expires'); + } + + return $this; + } + + /** + * Returns the value of the Expires header as a DateTime instance. + * + * @final + */ + public function getExpires(): ?\DateTimeInterface + { + try { + return $this->headers->getDate('Expires'); + } catch (\RuntimeException $e) { + // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past + return \DateTime::createFromFormat('U', time() - 172800); + } + } + + /** + * Sets the Expires HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @return $this + * + * @final + */ + public function setExpires(\DateTimeInterface $date = null): object + { + if (null === $date) { + $this->headers->remove('Expires'); + + return $this; + } + + if ($date instanceof \DateTime) { + $date = \DateTimeImmutable::createFromMutable($date); + } + + $date = $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the number of seconds after the time specified in the response's Date + * header when the response should no longer be considered fresh. + * + * First, it checks for a s-maxage directive, then a max-age directive, and then it falls + * back on an expires header. It returns null when no maximum age can be established. + * + * @final + */ + public function getMaxAge(): ?int + { + if ($this->headers->hasCacheControlDirective('s-maxage')) { + return (int) $this->headers->getCacheControlDirective('s-maxage'); + } + + if ($this->headers->hasCacheControlDirective('max-age')) { + return (int) $this->headers->getCacheControlDirective('max-age'); + } + + if (null !== $this->getExpires()) { + return (int) $this->getExpires()->format('U') - (int) $this->getDate()->format('U'); + } + + return null; + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh. + * + * This methods sets the Cache-Control max-age directive. + * + * @return $this + * + * @final + */ + public function setMaxAge(int $value): object + { + $this->headers->addCacheControlDirective('max-age', $value); + + return $this; + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh by shared caches. + * + * This methods sets the Cache-Control s-maxage directive. + * + * @return $this + * + * @final + */ + public function setSharedMaxAge(int $value): object + { + $this->setPublic(); + $this->headers->addCacheControlDirective('s-maxage', $value); + + return $this; + } + + /** + * Returns the response's time-to-live in seconds. + * + * It returns null when no freshness information is present in the response. + * + * When the responses TTL is <= 0, the response may not be served from cache without first + * revalidating with the origin. + * + * @final + */ + public function getTtl(): ?int + { + $maxAge = $this->getMaxAge(); + + return null !== $maxAge ? $maxAge - $this->getAge() : null; + } + + /** + * Sets the response's time-to-live for shared caches in seconds. + * + * This method adjusts the Cache-Control/s-maxage directive. + * + * @return $this + * + * @final + */ + public function setTtl(int $seconds): object + { + $this->setSharedMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Sets the response's time-to-live for private/client caches in seconds. + * + * This method adjusts the Cache-Control/max-age directive. + * + * @return $this + * + * @final + */ + public function setClientTtl(int $seconds): object + { + $this->setMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Returns the Last-Modified HTTP header as a DateTime instance. + * + * @throws \RuntimeException When the HTTP header is not parseable + * + * @final + */ + public function getLastModified(): ?\DateTimeInterface + { + return $this->headers->getDate('Last-Modified'); + } + + /** + * Sets the Last-Modified HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @return $this + * + * @final + */ + public function setLastModified(\DateTimeInterface $date = null): object + { + if (null === $date) { + $this->headers->remove('Last-Modified'); + + return $this; + } + + if ($date instanceof \DateTime) { + $date = \DateTimeImmutable::createFromMutable($date); + } + + $date = $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the literal value of the ETag HTTP header. + * + * @final + */ + public function getEtag(): ?string + { + return $this->headers->get('ETag'); + } + + /** + * Sets the ETag value. + * + * @param string|null $etag The ETag unique identifier or null to remove the header + * @param bool $weak Whether you want a weak ETag or not + * + * @return $this + * + * @final + */ + public function setEtag(string $etag = null, bool $weak = false): object + { + if (null === $etag) { + $this->headers->remove('Etag'); + } else { + if (0 !== strpos($etag, '"')) { + $etag = '"'.$etag.'"'; + } + + $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag); + } + + return $this; + } + + /** + * Sets the response's cache headers (validation and/or expiration). + * + * Available options are: etag, last_modified, max_age, s_maxage, private, public and immutable. + * + * @return $this + * + * @throws \InvalidArgumentException + * + * @final + */ + public function setCache(array $options): object + { + if ($diff = array_diff(array_keys($options), ['etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public', 'immutable'])) { + throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', $diff))); + } + + if (isset($options['etag'])) { + $this->setEtag($options['etag']); + } + + if (isset($options['last_modified'])) { + $this->setLastModified($options['last_modified']); + } + + if (isset($options['max_age'])) { + $this->setMaxAge($options['max_age']); + } + + if (isset($options['s_maxage'])) { + $this->setSharedMaxAge($options['s_maxage']); + } + + if (isset($options['public'])) { + if ($options['public']) { + $this->setPublic(); + } else { + $this->setPrivate(); + } + } + + if (isset($options['private'])) { + if ($options['private']) { + $this->setPrivate(); + } else { + $this->setPublic(); + } + } + + if (isset($options['immutable'])) { + $this->setImmutable((bool) $options['immutable']); + } + + return $this; + } + + /** + * Modifies the response so that it conforms to the rules defined for a 304 status code. + * + * This sets the status, removes the body, and discards any headers + * that MUST NOT be included in 304 responses. + * + * @return $this + * + * @see https://tools.ietf.org/html/rfc2616#section-10.3.5 + * + * @final + */ + public function setNotModified(): object + { + $this->setStatusCode(304); + $this->setContent(null); + + // remove headers that MUST NOT be included with 304 Not Modified responses + foreach (['Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified'] as $header) { + $this->headers->remove($header); + } + + return $this; + } + + /** + * Returns true if the response includes a Vary header. + * + * @final + */ + public function hasVary(): bool + { + return null !== $this->headers->get('Vary'); + } + + /** + * Returns an array of header names given in the Vary header. + * + * @final + */ + public function getVary(): array + { + if (!$vary = $this->headers->all('Vary')) { + return []; + } + + $ret = []; + foreach ($vary as $item) { + $ret = array_merge($ret, preg_split('/[\s,]+/', $item)); + } + + return $ret; + } + + /** + * Sets the Vary header. + * + * @param string|array $headers + * @param bool $replace Whether to replace the actual value or not (true by default) + * + * @return $this + * + * @final + */ + public function setVary($headers, bool $replace = true): object + { + $this->headers->set('Vary', $headers, $replace); + + return $this; + } + + /** + * Determines if the Response validators (ETag, Last-Modified) match + * a conditional value specified in the Request. + * + * If the Response is not modified, it sets the status code to 304 and + * removes the actual content by calling the setNotModified() method. + * + * @return bool true if the Response validators match the Request, false otherwise + * + * @final + */ + public function isNotModified(Request $request): bool + { + if (!$request->isMethodCacheable()) { + return false; + } + + $notModified = false; + $lastModified = $this->headers->get('Last-Modified'); + $modifiedSince = $request->headers->get('If-Modified-Since'); + + if ($etags = $request->getETags()) { + $notModified = \in_array($this->getEtag(), $etags) || \in_array('*', $etags); + } + + if ($modifiedSince && $lastModified) { + $notModified = strtotime($modifiedSince) >= strtotime($lastModified) && (!$etags || $notModified); + } + + if ($notModified) { + $this->setNotModified(); + } + + return $notModified; + } + + /** + * Is response invalid? + * + * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + * + * @final + */ + public function isInvalid(): bool + { + return $this->statusCode < 100 || $this->statusCode >= 600; + } + + /** + * Is response informative? + * + * @final + */ + public function isInformational(): bool + { + return $this->statusCode >= 100 && $this->statusCode < 200; + } + + /** + * Is response successful? + * + * @final + */ + public function isSuccessful(): bool + { + return $this->statusCode >= 200 && $this->statusCode < 300; + } + + /** + * Is the response a redirect? + * + * @final + */ + public function isRedirection(): bool + { + return $this->statusCode >= 300 && $this->statusCode < 400; + } + + /** + * Is there a client error? + * + * @final + */ + public function isClientError(): bool + { + return $this->statusCode >= 400 && $this->statusCode < 500; + } + + /** + * Was there a server side error? + * + * @final + */ + public function isServerError(): bool + { + return $this->statusCode >= 500 && $this->statusCode < 600; + } + + /** + * Is the response OK? + * + * @final + */ + public function isOk(): bool + { + return 200 === $this->statusCode; + } + + /** + * Is the response forbidden? + * + * @final + */ + public function isForbidden(): bool + { + return 403 === $this->statusCode; + } + + /** + * Is the response a not found error? + * + * @final + */ + public function isNotFound(): bool + { + return 404 === $this->statusCode; + } + + /** + * Is the response a redirect of some form? + * + * @final + */ + public function isRedirect(string $location = null): bool + { + return \in_array($this->statusCode, [201, 301, 302, 303, 307, 308]) && (null === $location ?: $location == $this->headers->get('Location')); + } + + /** + * Is the response empty? + * + * @final + */ + public function isEmpty(): bool + { + return \in_array($this->statusCode, [204, 304]); + } + + /** + * Cleans or flushes output buffers up to target level. + * + * Resulting level can be greater than target level if a non-removable buffer has been encountered. + * + * @final + */ + public static function closeOutputBuffers(int $targetLevel, bool $flush): void + { + $status = ob_get_status(true); + $level = \count($status); + $flags = PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE); + + while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) { + if ($flush) { + ob_end_flush(); + } else { + ob_end_clean(); + } + } + } + + /** + * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9. + * + * @see http://support.microsoft.com/kb/323308 + * + * @final + */ + protected function ensureIEOverSSLCompatibility(Request $request): void + { + if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) && true === $request->isSecure()) { + if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) { + $this->headers->remove('Cache-Control'); + } + } + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/ResponseHeaderBag.php b/lam/lib/3rdParty/composer/symfony/http-foundation/ResponseHeaderBag.php new file mode 100644 index 00000000..de1e2a16 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/ResponseHeaderBag.php @@ -0,0 +1,293 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ResponseHeaderBag is a container for Response HTTP headers. + * + * @author Fabien Potencier + */ +class ResponseHeaderBag extends HeaderBag +{ + const COOKIES_FLAT = 'flat'; + const COOKIES_ARRAY = 'array'; + + const DISPOSITION_ATTACHMENT = 'attachment'; + const DISPOSITION_INLINE = 'inline'; + + protected $computedCacheControl = []; + protected $cookies = []; + protected $headerNames = []; + + public function __construct(array $headers = []) + { + parent::__construct($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + + /* RFC2616 - 14.18 says all Responses need to have a Date */ + if (!isset($this->headers['date'])) { + $this->initDate(); + } + } + + /** + * Returns the headers, with original capitalizations. + * + * @return array An array of headers + */ + public function allPreserveCase() + { + $headers = []; + foreach ($this->all() as $name => $value) { + $headers[$this->headerNames[$name] ?? $name] = $value; + } + + return $headers; + } + + public function allPreserveCaseWithoutCookies() + { + $headers = $this->allPreserveCase(); + if (isset($this->headerNames['set-cookie'])) { + unset($headers[$this->headerNames['set-cookie']]); + } + + return $headers; + } + + /** + * {@inheritdoc} + */ + public function replace(array $headers = []) + { + $this->headerNames = []; + + parent::replace($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + + if (!isset($this->headers['date'])) { + $this->initDate(); + } + } + + /** + * {@inheritdoc} + */ + public function all(string $key = null) + { + $headers = parent::all(); + + if (null !== $key) { + $key = strtr($key, self::UPPER, self::LOWER); + + return 'set-cookie' !== $key ? $headers[$key] ?? [] : array_map('strval', $this->getCookies()); + } + + foreach ($this->getCookies() as $cookie) { + $headers['set-cookie'][] = (string) $cookie; + } + + return $headers; + } + + /** + * {@inheritdoc} + */ + public function set(string $key, $values, bool $replace = true) + { + $uniqueKey = strtr($key, self::UPPER, self::LOWER); + + if ('set-cookie' === $uniqueKey) { + if ($replace) { + $this->cookies = []; + } + foreach ((array) $values as $cookie) { + $this->setCookie(Cookie::fromString($cookie)); + } + $this->headerNames[$uniqueKey] = $key; + + return; + } + + $this->headerNames[$uniqueKey] = $key; + + parent::set($key, $values, $replace); + + // ensure the cache-control header has sensible defaults + if (\in_array($uniqueKey, ['cache-control', 'etag', 'last-modified', 'expires'], true) && '' !== $computed = $this->computeCacheControlValue()) { + $this->headers['cache-control'] = [$computed]; + $this->headerNames['cache-control'] = 'Cache-Control'; + $this->computedCacheControl = $this->parseCacheControl($computed); + } + } + + /** + * {@inheritdoc} + */ + public function remove(string $key) + { + $uniqueKey = strtr($key, self::UPPER, self::LOWER); + unset($this->headerNames[$uniqueKey]); + + if ('set-cookie' === $uniqueKey) { + $this->cookies = []; + + return; + } + + parent::remove($key); + + if ('cache-control' === $uniqueKey) { + $this->computedCacheControl = []; + } + + if ('date' === $uniqueKey) { + $this->initDate(); + } + } + + /** + * {@inheritdoc} + */ + public function hasCacheControlDirective(string $key) + { + return \array_key_exists($key, $this->computedCacheControl); + } + + /** + * {@inheritdoc} + */ + public function getCacheControlDirective(string $key) + { + return \array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null; + } + + public function setCookie(Cookie $cookie) + { + $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; + $this->headerNames['set-cookie'] = 'Set-Cookie'; + } + + /** + * Removes a cookie from the array, but does not unset it in the browser. + */ + public function removeCookie(string $name, ?string $path = '/', string $domain = null) + { + if (null === $path) { + $path = '/'; + } + + unset($this->cookies[$domain][$path][$name]); + + if (empty($this->cookies[$domain][$path])) { + unset($this->cookies[$domain][$path]); + + if (empty($this->cookies[$domain])) { + unset($this->cookies[$domain]); + } + } + + if (empty($this->cookies)) { + unset($this->headerNames['set-cookie']); + } + } + + /** + * Returns an array with all cookies. + * + * @return Cookie[] + * + * @throws \InvalidArgumentException When the $format is invalid + */ + public function getCookies(string $format = self::COOKIES_FLAT) + { + if (!\in_array($format, [self::COOKIES_FLAT, self::COOKIES_ARRAY])) { + throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY]))); + } + + if (self::COOKIES_ARRAY === $format) { + return $this->cookies; + } + + $flattenedCookies = []; + foreach ($this->cookies as $path) { + foreach ($path as $cookies) { + foreach ($cookies as $cookie) { + $flattenedCookies[] = $cookie; + } + } + } + + return $flattenedCookies; + } + + /** + * Clears a cookie in the browser. + */ + public function clearCookie(string $name, ?string $path = '/', string $domain = null, bool $secure = false, bool $httpOnly = true) + { + $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, null)); + } + + /** + * @see HeaderUtils::makeDisposition() + */ + public function makeDisposition(string $disposition, string $filename, string $filenameFallback = '') + { + return HeaderUtils::makeDisposition($disposition, $filename, $filenameFallback); + } + + /** + * Returns the calculated value of the cache-control header. + * + * This considers several other headers and calculates or modifies the + * cache-control header to a sensible, conservative value. + * + * @return string + */ + protected function computeCacheControlValue() + { + if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) { + return 'no-cache, private'; + } + + if (!$this->cacheControl) { + // conservative by default + return 'private, must-revalidate'; + } + + $header = $this->getCacheControlHeader(); + if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) { + return $header; + } + + // public if s-maxage is defined, private otherwise + if (!isset($this->cacheControl['s-maxage'])) { + return $header.', private'; + } + + return $header; + } + + private function initDate(): void + { + $now = \DateTime::createFromFormat('U', time()); + $now->setTimezone(new \DateTimeZone('UTC')); + $this->set('Date', $now->format('D, d M Y H:i:s').' GMT'); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/ServerBag.php b/lam/lib/3rdParty/composer/symfony/http-foundation/ServerBag.php new file mode 100644 index 00000000..25da35ec --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/ServerBag.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ServerBag is a container for HTTP headers from the $_SERVER variable. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Robert Kiss + */ +class ServerBag extends ParameterBag +{ + /** + * Gets the HTTP headers. + * + * @return array + */ + public function getHeaders() + { + $headers = []; + foreach ($this->parameters as $key => $value) { + if (0 === strpos($key, 'HTTP_')) { + $headers[substr($key, 5)] = $value; + } elseif (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) { + $headers[$key] = $value; + } + } + + if (isset($this->parameters['PHP_AUTH_USER'])) { + $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER']; + $headers['PHP_AUTH_PW'] = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : ''; + } else { + /* + * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default + * For this workaround to work, add these lines to your .htaccess file: + * RewriteCond %{HTTP:Authorization} ^(.+)$ + * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + * + * A sample .htaccess file: + * RewriteEngine On + * RewriteCond %{HTTP:Authorization} ^(.+)$ + * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + * RewriteCond %{REQUEST_FILENAME} !-f + * RewriteRule ^(.*)$ app.php [QSA,L] + */ + + $authorizationHeader = null; + if (isset($this->parameters['HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION']; + } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION']; + } + + if (null !== $authorizationHeader) { + if (0 === stripos($authorizationHeader, 'basic ')) { + // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic + $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2); + if (2 == \count($exploded)) { + list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded; + } + } elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) { + // In some circumstances PHP_AUTH_DIGEST needs to be set + $headers['PHP_AUTH_DIGEST'] = $authorizationHeader; + $this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader; + } elseif (0 === stripos($authorizationHeader, 'bearer ')) { + /* + * XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables, + * I'll just set $headers['AUTHORIZATION'] here. + * https://php.net/reserved.variables.server + */ + $headers['AUTHORIZATION'] = $authorizationHeader; + } + } + } + + if (isset($headers['AUTHORIZATION'])) { + return $headers; + } + + // PHP_AUTH_USER/PHP_AUTH_PW + if (isset($headers['PHP_AUTH_USER'])) { + $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']); + } elseif (isset($headers['PHP_AUTH_DIGEST'])) { + $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST']; + } + + return $headers; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/AttributeBag.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/AttributeBag.php new file mode 100644 index 00000000..aad6b610 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/AttributeBag.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +/** + * This class relates to session attribute storage. + */ +class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable +{ + private $name = 'attributes'; + private $storageKey; + + protected $attributes = []; + + /** + * @param string $storageKey The key used to store attributes in the session + */ + public function __construct(string $storageKey = '_sf2_attributes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName(string $name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$attributes) + { + $this->attributes = &$attributes; + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function has(string $name) + { + return \array_key_exists($name, $this->attributes); + } + + /** + * {@inheritdoc} + */ + public function get(string $name, $default = null) + { + return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set(string $name, $value) + { + $this->attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->attributes = []; + foreach ($attributes as $key => $value) { + $this->set($key, $value); + } + } + + /** + * {@inheritdoc} + */ + public function remove(string $name) + { + $retval = null; + if (\array_key_exists($name, $this->attributes)) { + $retval = $this->attributes[$name]; + unset($this->attributes[$name]); + } + + return $retval; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $return = $this->attributes; + $this->attributes = []; + + return $return; + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->attributes); + } + + /** + * Returns the number of attributes. + * + * @return int The number of attributes + */ + public function count() + { + return \count($this->attributes); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php new file mode 100644 index 00000000..7017b717 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Attributes store. + * + * @author Drak + */ +interface AttributeBagInterface extends SessionBagInterface +{ + /** + * Checks if an attribute is defined. + * + * @return bool true if the attribute is defined, false otherwise + */ + public function has(string $name); + + /** + * Returns an attribute. + * + * @param mixed $default The default value if not found + * + * @return mixed + */ + public function get(string $name, $default = null); + + /** + * Sets an attribute. + * + * @param mixed $value + */ + public function set(string $name, $value); + + /** + * Returns attributes. + * + * @return array + */ + public function all(); + + public function replace(array $attributes); + + /** + * Removes an attribute. + * + * @return mixed The removed value or null when it does not exist + */ + public function remove(string $name); +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php new file mode 100644 index 00000000..7e752dda --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +/** + * This class provides structured storage of session attributes using + * a name spacing character in the key. + * + * @author Drak + */ +class NamespacedAttributeBag extends AttributeBag +{ + private $namespaceCharacter; + + /** + * @param string $storageKey Session storage key + * @param string $namespaceCharacter Namespace character to use in keys + */ + public function __construct(string $storageKey = '_sf2_attributes', string $namespaceCharacter = '/') + { + $this->namespaceCharacter = $namespaceCharacter; + parent::__construct($storageKey); + } + + /** + * {@inheritdoc} + */ + public function has(string $name) + { + // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + if (null === $attributes) { + return false; + } + + return \array_key_exists($name, $attributes); + } + + /** + * {@inheritdoc} + */ + public function get(string $name, $default = null) + { + // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + if (null === $attributes) { + return $default; + } + + return \array_key_exists($name, $attributes) ? $attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set(string $name, $value) + { + $attributes = &$this->resolveAttributePath($name, true); + $name = $this->resolveKey($name); + $attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function remove(string $name) + { + $retval = null; + $attributes = &$this->resolveAttributePath($name); + $name = $this->resolveKey($name); + if (null !== $attributes && \array_key_exists($name, $attributes)) { + $retval = $attributes[$name]; + unset($attributes[$name]); + } + + return $retval; + } + + /** + * Resolves a path in attributes property and returns it as a reference. + * + * This method allows structured namespacing of session attributes. + * + * @param string $name Key name + * @param bool $writeContext Write context, default false + * + * @return array|null + */ + protected function &resolveAttributePath(string $name, bool $writeContext = false) + { + $array = &$this->attributes; + $name = (0 === strpos($name, $this->namespaceCharacter)) ? substr($name, 1) : $name; + + // Check if there is anything to do, else return + if (!$name) { + return $array; + } + + $parts = explode($this->namespaceCharacter, $name); + if (\count($parts) < 2) { + if (!$writeContext) { + return $array; + } + + $array[$parts[0]] = []; + + return $array; + } + + unset($parts[\count($parts) - 1]); + + foreach ($parts as $part) { + if (null !== $array && !\array_key_exists($part, $array)) { + if (!$writeContext) { + $null = null; + + return $null; + } + + $array[$part] = []; + } + + $array = &$array[$part]; + } + + return $array; + } + + /** + * Resolves the key from the name. + * + * This is the last part in a dot separated string. + * + * @return string + */ + protected function resolveKey(string $name) + { + if (false !== $pos = strrpos($name, $this->namespaceCharacter)) { + $name = substr($name, $pos + 1); + } + + return $name; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php new file mode 100644 index 00000000..2707d64e --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * AutoExpireFlashBag flash message container. + * + * @author Drak + */ +class AutoExpireFlashBag implements FlashBagInterface +{ + private $name = 'flashes'; + private $flashes = ['display' => [], 'new' => []]; + private $storageKey; + + /** + * @param string $storageKey The key used to store flashes in the session + */ + public function __construct(string $storageKey = '_symfony_flashes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName(string $name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + + // The logic: messages from the last request will be stored in new, so we move them to previous + // This request we will show what is in 'display'. What is placed into 'new' this time round will + // be moved to display next time round. + $this->flashes['display'] = \array_key_exists('new', $this->flashes) ? $this->flashes['new'] : []; + $this->flashes['new'] = []; + } + + /** + * {@inheritdoc} + */ + public function add(string $type, $message) + { + $this->flashes['new'][$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function peek(string $type, array $default = []) + { + return $this->has($type) ? $this->flashes['display'][$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return \array_key_exists('display', $this->flashes) ? (array) $this->flashes['display'] : []; + } + + /** + * {@inheritdoc} + */ + public function get(string $type, array $default = []) + { + $return = $default; + + if (!$this->has($type)) { + return $return; + } + + if (isset($this->flashes['display'][$type])) { + $return = $this->flashes['display'][$type]; + unset($this->flashes['display'][$type]); + } + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->flashes['display']; + $this->flashes['display'] = []; + + return $return; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes['new'] = $messages; + } + + /** + * {@inheritdoc} + */ + public function set(string $type, $messages) + { + $this->flashes['new'][$type] = (array) $messages; + } + + /** + * {@inheritdoc} + */ + public function has(string $type) + { + return \array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type]; + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes['display']); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->all(); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/FlashBag.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/FlashBag.php new file mode 100644 index 00000000..88df7508 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/FlashBag.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * FlashBag flash message container. + * + * @author Drak + */ +class FlashBag implements FlashBagInterface +{ + private $name = 'flashes'; + private $flashes = []; + private $storageKey; + + /** + * @param string $storageKey The key used to store flashes in the session + */ + public function __construct(string $storageKey = '_symfony_flashes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName(string $name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + } + + /** + * {@inheritdoc} + */ + public function add(string $type, $message) + { + $this->flashes[$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function peek(string $type, array $default = []) + { + return $this->has($type) ? $this->flashes[$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return $this->flashes; + } + + /** + * {@inheritdoc} + */ + public function get(string $type, array $default = []) + { + if (!$this->has($type)) { + return $default; + } + + $return = $this->flashes[$type]; + + unset($this->flashes[$type]); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->peekAll(); + $this->flashes = []; + + return $return; + } + + /** + * {@inheritdoc} + */ + public function set(string $type, $messages) + { + $this->flashes[$type] = (array) $messages; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes = $messages; + } + + /** + * {@inheritdoc} + */ + public function has(string $type) + { + return \array_key_exists($type, $this->flashes) && $this->flashes[$type]; + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->all(); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/FlashBagInterface.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/FlashBagInterface.php new file mode 100644 index 00000000..8713e71d --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/FlashBagInterface.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * FlashBagInterface. + * + * @author Drak + */ +interface FlashBagInterface extends SessionBagInterface +{ + /** + * Adds a flash message for the given type. + * + * @param mixed $message + */ + public function add(string $type, $message); + + /** + * Registers one or more messages for a given type. + * + * @param string|array $messages + */ + public function set(string $type, $messages); + + /** + * Gets flash messages for a given type. + * + * @param string $type Message category type + * @param array $default Default value if $type does not exist + * + * @return array + */ + public function peek(string $type, array $default = []); + + /** + * Gets all flash messages. + * + * @return array + */ + public function peekAll(); + + /** + * Gets and clears flash from the stack. + * + * @param array $default Default value if $type does not exist + * + * @return array + */ + public function get(string $type, array $default = []); + + /** + * Gets and clears flashes from the stack. + * + * @return array + */ + public function all(); + + /** + * Sets all flash messages. + */ + public function setAll(array $messages); + + /** + * Has flash messages for a given type? + * + * @return bool + */ + public function has(string $type); + + /** + * Returns a list of all defined types. + * + * @return array + */ + public function keys(); +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Session.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Session.php new file mode 100644 index 00000000..89071b86 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Session.php @@ -0,0 +1,268 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; + +/** + * @author Fabien Potencier + * @author Drak + */ +class Session implements SessionInterface, \IteratorAggregate, \Countable +{ + protected $storage; + + private $flashName; + private $attributeName; + private $data = []; + private $usageIndex = 0; + + public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) + { + $this->storage = $storage ?: new NativeSessionStorage(); + + $attributes = $attributes ?: new AttributeBag(); + $this->attributeName = $attributes->getName(); + $this->registerBag($attributes); + + $flashes = $flashes ?: new FlashBag(); + $this->flashName = $flashes->getName(); + $this->registerBag($flashes); + } + + /** + * {@inheritdoc} + */ + public function start() + { + return $this->storage->start(); + } + + /** + * {@inheritdoc} + */ + public function has(string $name) + { + return $this->getAttributeBag()->has($name); + } + + /** + * {@inheritdoc} + */ + public function get(string $name, $default = null) + { + return $this->getAttributeBag()->get($name, $default); + } + + /** + * {@inheritdoc} + */ + public function set(string $name, $value) + { + $this->getAttributeBag()->set($name, $value); + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->getAttributeBag()->all(); + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->getAttributeBag()->replace($attributes); + } + + /** + * {@inheritdoc} + */ + public function remove(string $name) + { + return $this->getAttributeBag()->remove($name); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->getAttributeBag()->clear(); + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->storage->isStarted(); + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->getAttributeBag()->all()); + } + + /** + * Returns the number of attributes. + * + * @return int + */ + public function count() + { + return \count($this->getAttributeBag()->all()); + } + + public function &getUsageIndex(): int + { + return $this->usageIndex; + } + + /** + * @internal + */ + public function isEmpty(): bool + { + if ($this->isStarted()) { + ++$this->usageIndex; + } + foreach ($this->data as &$data) { + if (!empty($data)) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function invalidate(int $lifetime = null) + { + $this->storage->clear(); + + return $this->migrate(true, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function migrate(bool $destroy = false, int $lifetime = null) + { + return $this->storage->regenerate($destroy, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function save() + { + $this->storage->save(); + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->storage->getId(); + } + + /** + * {@inheritdoc} + */ + public function setId(string $id) + { + if ($this->storage->getId() !== $id) { + $this->storage->setId($id); + } + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->storage->getName(); + } + + /** + * {@inheritdoc} + */ + public function setName(string $name) + { + $this->storage->setName($name); + } + + /** + * {@inheritdoc} + */ + public function getMetadataBag() + { + ++$this->usageIndex; + + return $this->storage->getMetadataBag(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex)); + } + + /** + * {@inheritdoc} + */ + public function getBag(string $name) + { + $bag = $this->storage->getBag($name); + + return method_exists($bag, 'getBag') ? $bag->getBag() : $bag; + } + + /** + * Gets the flashbag interface. + * + * @return FlashBagInterface + */ + public function getFlashBag() + { + return $this->getBag($this->flashName); + } + + /** + * Gets the attributebag interface. + * + * Note that this method was added to help with IDE autocompletion. + */ + private function getAttributeBag(): AttributeBagInterface + { + return $this->getBag($this->attributeName); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionBagInterface.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionBagInterface.php new file mode 100644 index 00000000..8e37d06d --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionBagInterface.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * Session Bag store. + * + * @author Drak + */ +interface SessionBagInterface +{ + /** + * Gets this bag's name. + * + * @return string + */ + public function getName(); + + /** + * Initializes the Bag. + */ + public function initialize(array &$array); + + /** + * Gets the storage key for this bag. + * + * @return string + */ + public function getStorageKey(); + + /** + * Clears out data from bag. + * + * @return mixed Whatever data was contained + */ + public function clear(); +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionBagProxy.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionBagProxy.php new file mode 100644 index 00000000..0ae8231e --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionBagProxy.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class SessionBagProxy implements SessionBagInterface +{ + private $bag; + private $data; + private $usageIndex; + + public function __construct(SessionBagInterface $bag, array &$data, ?int &$usageIndex) + { + $this->bag = $bag; + $this->data = &$data; + $this->usageIndex = &$usageIndex; + } + + public function getBag(): SessionBagInterface + { + ++$this->usageIndex; + + return $this->bag; + } + + public function isEmpty(): bool + { + if (!isset($this->data[$this->bag->getStorageKey()])) { + return true; + } + ++$this->usageIndex; + + return empty($this->data[$this->bag->getStorageKey()]); + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return $this->bag->getName(); + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$array): void + { + ++$this->usageIndex; + $this->data[$this->bag->getStorageKey()] = &$array; + + $this->bag->initialize($array); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey(): string + { + return $this->bag->getStorageKey(); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->bag->clear(); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionInterface.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionInterface.php new file mode 100644 index 00000000..b2f09fd0 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionInterface.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; + +/** + * Interface for the session. + * + * @author Drak + */ +interface SessionInterface +{ + /** + * Starts the session storage. + * + * @return bool + * + * @throws \RuntimeException if session fails to start + */ + public function start(); + + /** + * Returns the session ID. + * + * @return string + */ + public function getId(); + + /** + * Sets the session ID. + */ + public function setId(string $id); + + /** + * Returns the session name. + * + * @return string + */ + public function getName(); + + /** + * Sets the session name. + */ + public function setName(string $name); + + /** + * Invalidates the current session. + * + * Clears all session attributes and flashes and regenerates the + * session and deletes the old session from persistence. + * + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool + */ + public function invalidate(int $lifetime = null); + + /** + * Migrates the current session to a new session id while maintaining all + * session attributes. + * + * @param bool $destroy Whether to delete the old session or leave it to garbage collection + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool + */ + public function migrate(bool $destroy = false, int $lifetime = null); + + /** + * Force the session to be saved and closed. + * + * This method is generally not required for real sessions as + * the session will be automatically saved at the end of + * code execution. + */ + public function save(); + + /** + * Checks if an attribute is defined. + * + * @return bool + */ + public function has(string $name); + + /** + * Returns an attribute. + * + * @param mixed $default The default value if not found + * + * @return mixed + */ + public function get(string $name, $default = null); + + /** + * Sets an attribute. + * + * @param mixed $value + */ + public function set(string $name, $value); + + /** + * Returns attributes. + * + * @return array + */ + public function all(); + + /** + * Sets attributes. + */ + public function replace(array $attributes); + + /** + * Removes an attribute. + * + * @return mixed The removed value or null when it does not exist + */ + public function remove(string $name); + + /** + * Clears all attributes. + */ + public function clear(); + + /** + * Checks if the session was started. + * + * @return bool + */ + public function isStarted(); + + /** + * Registers a SessionBagInterface with the session. + */ + public function registerBag(SessionBagInterface $bag); + + /** + * Gets a bag instance by name. + * + * @return SessionBagInterface + */ + public function getBag(string $name); + + /** + * Gets session meta. + * + * @return MetadataBag + */ + public function getMetadataBag(); +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionUtils.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionUtils.php new file mode 100644 index 00000000..b5bce4a8 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionUtils.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * Session utility functions. + * + * @author Nicolas Grekas + * @author Rémon van de Kamp + * + * @internal + */ +final class SessionUtils +{ + /** + * Finds the session header amongst the headers that are to be sent, removes it, and returns + * it so the caller can process it further. + */ + public static function popSessionCookie(string $sessionName, string $sessionId): ?string + { + $sessionCookie = null; + $sessionCookiePrefix = sprintf(' %s=', urlencode($sessionName)); + $sessionCookieWithId = sprintf('%s%s;', $sessionCookiePrefix, urlencode($sessionId)); + $otherCookies = []; + foreach (headers_list() as $h) { + if (0 !== stripos($h, 'Set-Cookie:')) { + continue; + } + if (11 === strpos($h, $sessionCookiePrefix, 11)) { + $sessionCookie = $h; + + if (11 !== strpos($h, $sessionCookieWithId, 11)) { + $otherCookies[] = $h; + } + } else { + $otherCookies[] = $h; + } + } + if (null === $sessionCookie) { + return null; + } + + header_remove('Set-Cookie'); + foreach ($otherCookies as $h) { + header($h, false); + } + + return $sessionCookie; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php new file mode 100644 index 00000000..0618035b --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\SessionUtils; + +/** + * This abstract session handler provides a generic implementation + * of the PHP 7.0 SessionUpdateTimestampHandlerInterface, + * enabling strict and lazy session handling. + * + * @author Nicolas Grekas + */ +abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + private $sessionName; + private $prefetchId; + private $prefetchData; + private $newSessionId; + private $igbinaryEmptyData; + + /** + * @return bool + */ + public function open($savePath, $sessionName) + { + $this->sessionName = $sessionName; + if (!headers_sent() && !ini_get('session.cache_limiter') && '0' !== ini_get('session.cache_limiter')) { + header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) ini_get('session.cache_expire'))); + } + + return true; + } + + /** + * @return string + */ + abstract protected function doRead(string $sessionId); + + /** + * @return bool + */ + abstract protected function doWrite(string $sessionId, string $data); + + /** + * @return bool + */ + abstract protected function doDestroy(string $sessionId); + + /** + * @return bool + */ + public function validateId($sessionId) + { + $this->prefetchData = $this->read($sessionId); + $this->prefetchId = $sessionId; + + return '' !== $this->prefetchData; + } + + /** + * @return string + */ + public function read($sessionId) + { + if (null !== $this->prefetchId) { + $prefetchId = $this->prefetchId; + $prefetchData = $this->prefetchData; + $this->prefetchId = $this->prefetchData = null; + + if ($prefetchId === $sessionId || '' === $prefetchData) { + $this->newSessionId = '' === $prefetchData ? $sessionId : null; + + return $prefetchData; + } + } + + $data = $this->doRead($sessionId); + $this->newSessionId = '' === $data ? $sessionId : null; + + return $data; + } + + /** + * @return bool + */ + public function write($sessionId, $data) + { + if (null === $this->igbinaryEmptyData) { + // see https://github.com/igbinary/igbinary/issues/146 + $this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize([]) : ''; + } + if ('' === $data || $this->igbinaryEmptyData === $data) { + return $this->destroy($sessionId); + } + $this->newSessionId = null; + + return $this->doWrite($sessionId, $data); + } + + /** + * @return bool + */ + public function destroy($sessionId) + { + if (!headers_sent() && filter_var(ini_get('session.use_cookies'), FILTER_VALIDATE_BOOLEAN)) { + if (!$this->sessionName) { + throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', \get_class($this))); + } + $cookie = SessionUtils::popSessionCookie($this->sessionName, $sessionId); + + /* + * We send an invalidation Set-Cookie header (zero lifetime) + * when either the session was started or a cookie with + * the session name was sent by the client (in which case + * we know it's invalid as a valid session cookie would've + * started the session). + */ + if (null === $cookie || isset($_COOKIE[$this->sessionName])) { + if (\PHP_VERSION_ID < 70300) { + setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), filter_var(ini_get('session.cookie_secure'), FILTER_VALIDATE_BOOLEAN), filter_var(ini_get('session.cookie_httponly'), FILTER_VALIDATE_BOOLEAN)); + } else { + $params = session_get_cookie_params(); + unset($params['lifetime']); + setcookie($this->sessionName, '', $params); + } + } + } + + return $this->newSessionId === $sessionId || $this->doDestroy($sessionId); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php new file mode 100644 index 00000000..8896964e --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Memcached based session storage handler based on the Memcached class + * provided by the PHP memcached extension. + * + * @see https://php.net/memcached + * + * @author Drak + */ +class MemcachedSessionHandler extends AbstractSessionHandler +{ + private $memcached; + + /** + * @var int Time to live in seconds + */ + private $ttl; + + /** + * @var string Key prefix for shared environments + */ + private $prefix; + + /** + * Constructor. + * + * List of available options: + * * prefix: The prefix to use for the memcached keys in order to avoid collision + * * expiretime: The time to live in seconds. + * + * @throws \InvalidArgumentException When unsupported options are passed + */ + public function __construct(\Memcached $memcached, array $options = []) + { + $this->memcached = $memcached; + + if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime'])) { + throw new \InvalidArgumentException(sprintf('The following options are not supported "%s"', implode(', ', $diff))); + } + + $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400; + $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s'; + } + + /** + * @return bool + */ + public function close() + { + return $this->memcached->quit(); + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId) + { + return $this->memcached->get($this->prefix.$sessionId) ?: ''; + } + + /** + * @return bool + */ + public function updateTimestamp($sessionId, $data) + { + $this->memcached->touch($this->prefix.$sessionId, time() + $this->ttl); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data) + { + return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl); + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId) + { + $result = $this->memcached->delete($this->prefix.$sessionId); + + return $result || \Memcached::RES_NOTFOUND == $this->memcached->getResultCode(); + } + + /** + * @return bool + */ + public function gc($maxlifetime) + { + // not required here because memcached will auto expire the records anyhow. + return true; + } + + /** + * Return a Memcached instance. + * + * @return \Memcached + */ + protected function getMemcached() + { + return $this->memcached; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php new file mode 100644 index 00000000..c6b16d11 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Migrating session handler for migrating from one handler to another. It reads + * from the current handler and writes both the current and new ones. + * + * It ignores errors from the new handler. + * + * @author Ross Motley + * @author Oliver Radwell + */ +class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + private $currentHandler; + private $writeOnlyHandler; + + public function __construct(\SessionHandlerInterface $currentHandler, \SessionHandlerInterface $writeOnlyHandler) + { + if (!$currentHandler instanceof \SessionUpdateTimestampHandlerInterface) { + $currentHandler = new StrictSessionHandler($currentHandler); + } + if (!$writeOnlyHandler instanceof \SessionUpdateTimestampHandlerInterface) { + $writeOnlyHandler = new StrictSessionHandler($writeOnlyHandler); + } + + $this->currentHandler = $currentHandler; + $this->writeOnlyHandler = $writeOnlyHandler; + } + + /** + * @return bool + */ + public function close() + { + $result = $this->currentHandler->close(); + $this->writeOnlyHandler->close(); + + return $result; + } + + /** + * @return bool + */ + public function destroy($sessionId) + { + $result = $this->currentHandler->destroy($sessionId); + $this->writeOnlyHandler->destroy($sessionId); + + return $result; + } + + /** + * @return bool + */ + public function gc($maxlifetime) + { + $result = $this->currentHandler->gc($maxlifetime); + $this->writeOnlyHandler->gc($maxlifetime); + + return $result; + } + + /** + * @return bool + */ + public function open($savePath, $sessionName) + { + $result = $this->currentHandler->open($savePath, $sessionName); + $this->writeOnlyHandler->open($savePath, $sessionName); + + return $result; + } + + /** + * @return string + */ + public function read($sessionId) + { + // No reading from new handler until switch-over + return $this->currentHandler->read($sessionId); + } + + /** + * @return bool + */ + public function write($sessionId, $sessionData) + { + $result = $this->currentHandler->write($sessionId, $sessionData); + $this->writeOnlyHandler->write($sessionId, $sessionData); + + return $result; + } + + /** + * @return bool + */ + public function validateId($sessionId) + { + // No reading from new handler until switch-over + return $this->currentHandler->validateId($sessionId); + } + + /** + * @return bool + */ + public function updateTimestamp($sessionId, $sessionData) + { + $result = $this->currentHandler->updateTimestamp($sessionId, $sessionData); + $this->writeOnlyHandler->updateTimestamp($sessionId, $sessionData); + + return $result; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php new file mode 100644 index 00000000..a6889e40 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -0,0 +1,187 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Session handler using the mongodb/mongodb package and MongoDB driver extension. + * + * @author Markus Bachmann + * + * @see https://packagist.org/packages/mongodb/mongodb + * @see https://php.net/mongodb + */ +class MongoDbSessionHandler extends AbstractSessionHandler +{ + private $mongo; + + /** + * @var \MongoDB\Collection + */ + private $collection; + + /** + * @var array + */ + private $options; + + /** + * Constructor. + * + * List of available options: + * * database: The name of the database [required] + * * collection: The name of the collection [required] + * * id_field: The field name for storing the session id [default: _id] + * * data_field: The field name for storing the session data [default: data] + * * time_field: The field name for storing the timestamp [default: time] + * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at]. + * + * It is strongly recommended to put an index on the `expiry_field` for + * garbage-collection. Alternatively it's possible to automatically expire + * the sessions in the database as described below: + * + * A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions + * automatically. Such an index can for example look like this: + * + * db..ensureIndex( + * { "": 1 }, + * { "expireAfterSeconds": 0 } + * ) + * + * More details on: https://docs.mongodb.org/manual/tutorial/expire-data/ + * + * If you use such an index, you can drop `gc_probability` to 0 since + * no garbage-collection is required. + * + * @throws \InvalidArgumentException When "database" or "collection" not provided + */ + public function __construct(\MongoDB\Client $mongo, array $options) + { + if (!isset($options['database']) || !isset($options['collection'])) { + throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler'); + } + + $this->mongo = $mongo; + + $this->options = array_merge([ + 'id_field' => '_id', + 'data_field' => 'data', + 'time_field' => 'time', + 'expiry_field' => 'expires_at', + ], $options); + } + + /** + * @return bool + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId) + { + $this->getCollection()->deleteOne([ + $this->options['id_field'] => $sessionId, + ]); + + return true; + } + + /** + * @return bool + */ + public function gc($maxlifetime) + { + $this->getCollection()->deleteMany([ + $this->options['expiry_field'] => ['$lt' => new \MongoDB\BSON\UTCDateTime()], + ]); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data) + { + $expiry = new \MongoDB\BSON\UTCDateTime((time() + (int) ini_get('session.gc_maxlifetime')) * 1000); + + $fields = [ + $this->options['time_field'] => new \MongoDB\BSON\UTCDateTime(), + $this->options['expiry_field'] => $expiry, + $this->options['data_field'] => new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY), + ]; + + $this->getCollection()->updateOne( + [$this->options['id_field'] => $sessionId], + ['$set' => $fields], + ['upsert' => true] + ); + + return true; + } + + /** + * @return bool + */ + public function updateTimestamp($sessionId, $data) + { + $expiry = new \MongoDB\BSON\UTCDateTime((time() + (int) ini_get('session.gc_maxlifetime')) * 1000); + + $this->getCollection()->updateOne( + [$this->options['id_field'] => $sessionId], + ['$set' => [ + $this->options['time_field'] => new \MongoDB\BSON\UTCDateTime(), + $this->options['expiry_field'] => $expiry, + ]] + ); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId) + { + $dbData = $this->getCollection()->findOne([ + $this->options['id_field'] => $sessionId, + $this->options['expiry_field'] => ['$gte' => new \MongoDB\BSON\UTCDateTime()], + ]); + + if (null === $dbData) { + return ''; + } + + return $dbData[$this->options['data_field']]->getData(); + } + + private function getCollection(): \MongoDB\Collection + { + if (null === $this->collection) { + $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']); + } + + return $this->collection; + } + + /** + * @return \MongoDB\Client + */ + protected function getMongo() + { + return $this->mongo; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php new file mode 100644 index 00000000..bdfc9d81 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Native session handler using PHP's built in file storage. + * + * @author Drak + */ +class NativeFileSessionHandler extends \SessionHandler +{ + /** + * @param string $savePath Path of directory to save session files + * Default null will leave setting as defined by PHP. + * '/path', 'N;/path', or 'N;octal-mode;/path + * + * @see https://php.net/session.configuration#ini.session.save-path for further details. + * + * @throws \InvalidArgumentException On invalid $savePath + * @throws \RuntimeException When failing to create the save directory + */ + public function __construct(string $savePath = null) + { + if (null === $savePath) { + $savePath = ini_get('session.save_path'); + } + + $baseDir = $savePath; + + if ($count = substr_count($savePath, ';')) { + if ($count > 2) { + throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'', $savePath)); + } + + // characters after last ';' are the path + $baseDir = ltrim(strrchr($savePath, ';'), ';'); + } + + if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) { + throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $baseDir)); + } + + ini_set('session.save_path', $savePath); + ini_set('session.save_handler', 'files'); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php new file mode 100644 index 00000000..aa0e595c --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Can be used in unit testing or in a situations where persisted sessions are not desired. + * + * @author Drak + */ +class NullSessionHandler extends AbstractSessionHandler +{ + /** + * @return bool + */ + public function close() + { + return true; + } + + /** + * @return bool + */ + public function validateId($sessionId) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId) + { + return ''; + } + + /** + * @return bool + */ + public function updateTimestamp($sessionId, $data) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId) + { + return true; + } + + /** + * @return bool + */ + public function gc($maxlifetime) + { + return true; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php new file mode 100644 index 00000000..52f8334a --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php @@ -0,0 +1,899 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Session handler using a PDO connection to read and write data. + * + * It works with MySQL, PostgreSQL, Oracle, SQL Server and SQLite and implements + * different locking strategies to handle concurrent access to the same session. + * Locking is necessary to prevent loss of data due to race conditions and to keep + * the session data consistent between read() and write(). With locking, requests + * for the same session will wait until the other one finished writing. For this + * reason it's best practice to close a session as early as possible to improve + * concurrency. PHPs internal files session handler also implements locking. + * + * Attention: Since SQLite does not support row level locks but locks the whole database, + * it means only one session can be accessed at a time. Even different sessions would wait + * for another to finish. So saving session in SQLite should only be considered for + * development or prototypes. + * + * Session data is a binary string that can contain non-printable characters like the null byte. + * For this reason it must be saved in a binary column in the database like BLOB in MySQL. + * Saving it in a character column could corrupt the data. You can use createTable() + * to initialize a correctly defined table. + * + * @see https://php.net/sessionhandlerinterface + * + * @author Fabien Potencier + * @author Michael Williams + * @author Tobias Schultze + */ +class PdoSessionHandler extends AbstractSessionHandler +{ + /** + * No locking is done. This means sessions are prone to loss of data due to + * race conditions of concurrent requests to the same session. The last session + * write will win in this case. It might be useful when you implement your own + * logic to deal with this like an optimistic approach. + */ + const LOCK_NONE = 0; + + /** + * Creates an application-level lock on a session. The disadvantage is that the + * lock is not enforced by the database and thus other, unaware parts of the + * application could still concurrently modify the session. The advantage is it + * does not require a transaction. + * This mode is not available for SQLite and not yet implemented for oci and sqlsrv. + */ + const LOCK_ADVISORY = 1; + + /** + * Issues a real row lock. Since it uses a transaction between opening and + * closing a session, you have to be careful when you use same database connection + * that you also use for your application logic. This mode is the default because + * it's the only reliable solution across DBMSs. + */ + const LOCK_TRANSACTIONAL = 2; + + private const MAX_LIFETIME = 315576000; + + /** + * @var \PDO|null PDO instance or null when not connected yet + */ + private $pdo; + + /** + * @var string|false|null DSN string or null for session.save_path or false when lazy connection disabled + */ + private $dsn = false; + + /** + * @var string Database driver + */ + private $driver; + + /** + * @var string Table name + */ + private $table = 'sessions'; + + /** + * @var string Column for session id + */ + private $idCol = 'sess_id'; + + /** + * @var string Column for session data + */ + private $dataCol = 'sess_data'; + + /** + * @var string Column for lifetime + */ + private $lifetimeCol = 'sess_lifetime'; + + /** + * @var string Column for timestamp + */ + private $timeCol = 'sess_time'; + + /** + * @var string Username when lazy-connect + */ + private $username = ''; + + /** + * @var string Password when lazy-connect + */ + private $password = ''; + + /** + * @var array Connection options when lazy-connect + */ + private $connectionOptions = []; + + /** + * @var int The strategy for locking, see constants + */ + private $lockMode = self::LOCK_TRANSACTIONAL; + + /** + * It's an array to support multiple reads before closing which is manual, non-standard usage. + * + * @var \PDOStatement[] An array of statements to release advisory locks + */ + private $unlockStatements = []; + + /** + * @var bool True when the current session exists but expired according to session.gc_maxlifetime + */ + private $sessionExpired = false; + + /** + * @var bool Whether a transaction is active + */ + private $inTransaction = false; + + /** + * @var bool Whether gc() has been called + */ + private $gcCalled = false; + + /** + * You can either pass an existing database connection as PDO instance or + * pass a DSN string that will be used to lazy-connect to the database + * when the session is actually used. Furthermore it's possible to pass null + * which will then use the session.save_path ini setting as PDO DSN parameter. + * + * List of available options: + * * db_table: The name of the table [default: sessions] + * * db_id_col: The column where to store the session id [default: sess_id] + * * db_data_col: The column where to store the session data [default: sess_data] + * * db_lifetime_col: The column where to store the lifetime [default: sess_lifetime] + * * db_time_col: The column where to store the timestamp [default: sess_time] + * * db_username: The username when lazy-connect [default: ''] + * * db_password: The password when lazy-connect [default: ''] + * * db_connection_options: An array of driver-specific connection options [default: []] + * * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL] + * + * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null + * + * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION + */ + public function __construct($pdoOrDsn = null, array $options = []) + { + if ($pdoOrDsn instanceof \PDO) { + if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) { + throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__)); + } + + $this->pdo = $pdoOrDsn; + $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } elseif (\is_string($pdoOrDsn) && false !== strpos($pdoOrDsn, '://')) { + $this->dsn = $this->buildDsnFromUrl($pdoOrDsn); + } else { + $this->dsn = $pdoOrDsn; + } + + $this->table = isset($options['db_table']) ? $options['db_table'] : $this->table; + $this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol; + $this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol; + $this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol; + $this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol; + $this->username = isset($options['db_username']) ? $options['db_username'] : $this->username; + $this->password = isset($options['db_password']) ? $options['db_password'] : $this->password; + $this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions; + $this->lockMode = isset($options['lock_mode']) ? $options['lock_mode'] : $this->lockMode; + } + + /** + * Creates the table to store sessions which can be called once for setup. + * + * Session ID is saved in a column of maximum length 128 because that is enough even + * for a 512 bit configured session.hash_function like Whirlpool. Session data is + * saved in a BLOB. One could also use a shorter inlined varbinary column + * if one was sure the data fits into it. + * + * @throws \PDOException When the table already exists + * @throws \DomainException When an unsupported PDO driver is used + */ + public function createTable() + { + // connect if we are not yet + $this->getConnection(); + + switch ($this->driver) { + case 'mysql': + // We use varbinary for the ID column because it prevents unwanted conversions: + // - character set conversions between server and client + // - trailing space removal + // - case-insensitivity + // - language processing like é == e + $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB"; + break; + case 'sqlite': + $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'pgsql': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'oci': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'sqlsrv': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + default: + throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)); + } + + try { + $this->pdo->exec($sql); + $this->pdo->exec("CREATE INDEX EXPIRY ON $this->table ($this->lifetimeCol)"); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + + /** + * Returns true when the current session exists but expired according to session.gc_maxlifetime. + * + * Can be used to distinguish between a new session and one that expired due to inactivity. + * + * @return bool Whether current session expired + */ + public function isSessionExpired() + { + return $this->sessionExpired; + } + + /** + * @return bool + */ + public function open($savePath, $sessionName) + { + $this->sessionExpired = false; + + if (null === $this->pdo) { + $this->connect($this->dsn ?: $savePath); + } + + return parent::open($savePath, $sessionName); + } + + /** + * @return string + */ + public function read($sessionId) + { + try { + return parent::read($sessionId); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + + /** + * @return bool + */ + public function gc($maxlifetime) + { + // We delay gc() to close() so that it is executed outside the transactional and blocking read-write process. + // This way, pruning expired sessions does not block them from being started while the current session is used. + $this->gcCalled = true; + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId) + { + // delete the record associated with this id + $sql = "DELETE FROM $this->table WHERE $this->idCol = :id"; + + try { + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->execute(); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data) + { + $maxlifetime = (int) ini_get('session.gc_maxlifetime'); + + try { + // We use a single MERGE SQL query when supported by the database. + $mergeStmt = $this->getMergeStatement($sessionId, $data, $maxlifetime); + if (null !== $mergeStmt) { + $mergeStmt->execute(); + + return true; + } + + $updateStmt = $this->getUpdateStatement($sessionId, $data, $maxlifetime); + $updateStmt->execute(); + + // When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in + // duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior). + // We can just catch such an error and re-execute the update. This is similar to a serializable + // transaction with retry logic on serialization failures but without the overhead and without possible + // false positives due to longer gap locking. + if (!$updateStmt->rowCount()) { + try { + $insertStmt = $this->getInsertStatement($sessionId, $data, $maxlifetime); + $insertStmt->execute(); + } catch (\PDOException $e) { + // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys + if (0 === strpos($e->getCode(), '23')) { + $updateStmt->execute(); + } else { + throw $e; + } + } + } + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + /** + * @return bool + */ + public function updateTimestamp($sessionId, $data) + { + $expiry = time() + (int) ini_get('session.gc_maxlifetime'); + + try { + $updateStmt = $this->pdo->prepare( + "UPDATE $this->table SET $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id" + ); + $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $updateStmt->bindParam(':expiry', $expiry, \PDO::PARAM_INT); + $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); + $updateStmt->execute(); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + /** + * @return bool + */ + public function close() + { + $this->commit(); + + while ($unlockStmt = array_shift($this->unlockStatements)) { + $unlockStmt->execute(); + } + + if ($this->gcCalled) { + $this->gcCalled = false; + + // delete the session records that have expired + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time AND $this->lifetimeCol > :min"; + $stmt = $this->pdo->prepare($sql); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT); + $stmt->execute(); + // to be removed in 6.0 + if ('mysql' === $this->driver) { + $legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol + $this->timeCol < :time"; + } else { + $legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol < :time - $this->timeCol"; + } + + $stmt = $this->pdo->prepare($legacySql); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT); + $stmt->execute(); + } + + if (false !== $this->dsn) { + $this->pdo = null; // only close lazy-connection + } + + return true; + } + + /** + * Lazy-connects to the database. + */ + private function connect(string $dsn): void + { + $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions); + $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } + + /** + * Builds a PDO DSN from a URL-like connection string. + * + * @todo implement missing support for oci DSN (which look totally different from other PDO ones) + */ + private function buildDsnFromUrl(string $dsnOrUrl): string + { + // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid + $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl); + + $params = parse_url($url); + + if (false === $params) { + return $dsnOrUrl; // If the URL is not valid, let's assume it might be a DSN already. + } + + $params = array_map('rawurldecode', $params); + + // Override the default username and password. Values passed through options will still win over these in the constructor. + if (isset($params['user'])) { + $this->username = $params['user']; + } + + if (isset($params['pass'])) { + $this->password = $params['pass']; + } + + if (!isset($params['scheme'])) { + throw new \InvalidArgumentException('URLs without scheme are not supported to configure the PdoSessionHandler'); + } + + $driverAliasMap = [ + 'mssql' => 'sqlsrv', + 'mysql2' => 'mysql', // Amazon RDS, for some weird reason + 'postgres' => 'pgsql', + 'postgresql' => 'pgsql', + 'sqlite3' => 'sqlite', + ]; + + $driver = isset($driverAliasMap[$params['scheme']]) ? $driverAliasMap[$params['scheme']] : $params['scheme']; + + // Doctrine DBAL supports passing its internal pdo_* driver names directly too (allowing both dashes and underscores). This allows supporting the same here. + if (0 === strpos($driver, 'pdo_') || 0 === strpos($driver, 'pdo-')) { + $driver = substr($driver, 4); + } + + switch ($driver) { + case 'mysql': + case 'pgsql': + $dsn = $driver.':'; + + if (isset($params['host']) && '' !== $params['host']) { + $dsn .= 'host='.$params['host'].';'; + } + + if (isset($params['port']) && '' !== $params['port']) { + $dsn .= 'port='.$params['port'].';'; + } + + if (isset($params['path'])) { + $dbName = substr($params['path'], 1); // Remove the leading slash + $dsn .= 'dbname='.$dbName.';'; + } + + return $dsn; + + case 'sqlite': + return 'sqlite:'.substr($params['path'], 1); + + case 'sqlsrv': + $dsn = 'sqlsrv:server='; + + if (isset($params['host'])) { + $dsn .= $params['host']; + } + + if (isset($params['port']) && '' !== $params['port']) { + $dsn .= ','.$params['port']; + } + + if (isset($params['path'])) { + $dbName = substr($params['path'], 1); // Remove the leading slash + $dsn .= ';Database='.$dbName; + } + + return $dsn; + + default: + throw new \InvalidArgumentException(sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme'])); + } + } + + /** + * Helper method to begin a transaction. + * + * Since SQLite does not support row level locks, we have to acquire a reserved lock + * on the database immediately. Because of https://bugs.php.net/42766 we have to create + * such a transaction manually which also means we cannot use PDO::commit or + * PDO::rollback or PDO::inTransaction for SQLite. + * + * Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions + * due to https://percona.com/blog/2013/12/12/one-more-innodb-gap-lock-to-avoid/ . + * So we change it to READ COMMITTED. + */ + private function beginTransaction(): void + { + if (!$this->inTransaction) { + if ('sqlite' === $this->driver) { + $this->pdo->exec('BEGIN IMMEDIATE TRANSACTION'); + } else { + if ('mysql' === $this->driver) { + $this->pdo->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED'); + } + $this->pdo->beginTransaction(); + } + $this->inTransaction = true; + } + } + + /** + * Helper method to commit a transaction. + */ + private function commit(): void + { + if ($this->inTransaction) { + try { + // commit read-write transaction which also releases the lock + if ('sqlite' === $this->driver) { + $this->pdo->exec('COMMIT'); + } else { + $this->pdo->commit(); + } + $this->inTransaction = false; + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + } + + /** + * Helper method to rollback a transaction. + */ + private function rollback(): void + { + // We only need to rollback if we are in a transaction. Otherwise the resulting + // error would hide the real problem why rollback was called. We might not be + // in a transaction when not using the transactional locking behavior or when + // two callbacks (e.g. destroy and write) are invoked that both fail. + if ($this->inTransaction) { + if ('sqlite' === $this->driver) { + $this->pdo->exec('ROLLBACK'); + } else { + $this->pdo->rollBack(); + } + $this->inTransaction = false; + } + } + + /** + * Reads the session data in respect to the different locking strategies. + * + * We need to make sure we do not return session data that is already considered garbage according + * to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes. + * + * @return string + */ + protected function doRead(string $sessionId) + { + if (self::LOCK_ADVISORY === $this->lockMode) { + $this->unlockStatements[] = $this->doAdvisoryLock($sessionId); + } + + $selectSql = $this->getSelectSql(); + $selectStmt = $this->pdo->prepare($selectSql); + $selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $insertStmt = null; + + do { + $selectStmt->execute(); + $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM); + + if ($sessionRows) { + $expiry = (int) $sessionRows[0][1]; + if ($expiry <= self::MAX_LIFETIME) { + $expiry += $sessionRows[0][2]; + } + + if ($expiry < time()) { + $this->sessionExpired = true; + + return ''; + } + + return \is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0]; + } + + if (null !== $insertStmt) { + $this->rollback(); + throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.'); + } + + if (!filter_var(ini_get('session.use_strict_mode'), FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { + // In strict mode, session fixation is not possible: new sessions always start with a unique + // random id, so that concurrency is not possible and this code path can be skipped. + // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block + // until other connections to the session are committed. + try { + $insertStmt = $this->getInsertStatement($sessionId, '', 0); + $insertStmt->execute(); + } catch (\PDOException $e) { + // Catch duplicate key error because other connection created the session already. + // It would only not be the case when the other connection destroyed the session. + if (0 === strpos($e->getCode(), '23')) { + // Retrieve finished session data written by concurrent connection by restarting the loop. + // We have to start a new transaction as a failed query will mark the current transaction as + // aborted in PostgreSQL and disallow further queries within it. + $this->rollback(); + $this->beginTransaction(); + continue; + } + + throw $e; + } + } + + return ''; + } while (true); + } + + /** + * Executes an application-level lock on the database. + * + * @return \PDOStatement The statement that needs to be executed later to release the lock + * + * @throws \DomainException When an unsupported PDO driver is used + * + * @todo implement missing advisory locks + * - for oci using DBMS_LOCK.REQUEST + * - for sqlsrv using sp_getapplock with LockOwner = Session + */ + private function doAdvisoryLock(string $sessionId): \PDOStatement + { + switch ($this->driver) { + case 'mysql': + // MySQL 5.7.5 and later enforces a maximum length on lock names of 64 characters. Previously, no limit was enforced. + $lockId = substr($sessionId, 0, 64); + // should we handle the return value? 0 on timeout, null on error + // we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout + $stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)'); + $stmt->bindValue(':key', $lockId, \PDO::PARAM_STR); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)'); + $releaseStmt->bindValue(':key', $lockId, \PDO::PARAM_STR); + + return $releaseStmt; + case 'pgsql': + // Obtaining an exclusive session level advisory lock requires an integer key. + // When session.sid_bits_per_character > 4, the session id can contain non-hex-characters. + // So we cannot just use hexdec(). + if (4 === \PHP_INT_SIZE) { + $sessionInt1 = $this->convertStringToInt($sessionId); + $sessionInt2 = $this->convertStringToInt(substr($sessionId, 4, 4)); + + $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key1, :key2)'); + $stmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); + $stmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key1, :key2)'); + $releaseStmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); + $releaseStmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); + } else { + $sessionBigInt = $this->convertStringToInt($sessionId); + + $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key)'); + $stmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key)'); + $releaseStmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); + } + + return $releaseStmt; + case 'sqlite': + throw new \DomainException('SQLite does not support advisory locks.'); + default: + throw new \DomainException(sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver)); + } + } + + /** + * Encodes the first 4 (when PHP_INT_SIZE == 4) or 8 characters of the string as an integer. + * + * Keep in mind, PHP integers are signed. + */ + private function convertStringToInt(string $string): int + { + if (4 === \PHP_INT_SIZE) { + return (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]); + } + + $int1 = (\ord($string[7]) << 24) + (\ord($string[6]) << 16) + (\ord($string[5]) << 8) + \ord($string[4]); + $int2 = (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]); + + return $int2 + ($int1 << 32); + } + + /** + * Return a locking or nonlocking SQL query to read session information. + * + * @throws \DomainException When an unsupported PDO driver is used + */ + private function getSelectSql(): string + { + if (self::LOCK_TRANSACTIONAL === $this->lockMode) { + $this->beginTransaction(); + + // selecting the time column should be removed in 6.0 + switch ($this->driver) { + case 'mysql': + case 'oci': + case 'pgsql': + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id FOR UPDATE"; + case 'sqlsrv': + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WITH (UPDLOCK, ROWLOCK) WHERE $this->idCol = :id"; + case 'sqlite': + // we already locked when starting transaction + break; + default: + throw new \DomainException(sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver)); + } + } + + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id"; + } + + /** + * Returns an insert statement supported by the database for writing session data. + */ + private function getInsertStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement + { + switch ($this->driver) { + case 'oci': + $data = fopen('php://memory', 'r+'); + fwrite($data, $sessionData); + rewind($data); + $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, EMPTY_BLOB(), :expiry, :time) RETURNING $this->dataCol into :data"; + break; + default: + $data = $sessionData; + $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)"; + break; + } + + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + + return $stmt; + } + + /** + * Returns an update statement supported by the database for writing session data. + */ + private function getUpdateStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement + { + switch ($this->driver) { + case 'oci': + $data = fopen('php://memory', 'r+'); + fwrite($data, $sessionData); + rewind($data); + $sql = "UPDATE $this->table SET $this->dataCol = EMPTY_BLOB(), $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id RETURNING $this->dataCol into :data"; + break; + default: + $data = $sessionData; + $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id"; + break; + } + + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + + return $stmt; + } + + /** + * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data. + */ + private function getMergeStatement(string $sessionId, string $data, int $maxlifetime): ?\PDOStatement + { + switch (true) { + case 'mysql' === $this->driver: + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ". + "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; + break; + case 'sqlsrv' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='): + // MERGE is only available since SQL Server 2008 and must be terminated by semicolon + // It also requires HOLDLOCK according to https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/ + $mergeSql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;"; + break; + case 'sqlite' === $this->driver: + $mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)"; + break; + case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='): + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ". + "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; + break; + default: + // MERGE is not supported with LOBs: https://oracle.com/technetwork/articles/fuecks-lobs-095315.html + return null; + } + + $mergeStmt = $this->pdo->prepare($mergeSql); + + if ('sqlsrv' === $this->driver) { + $mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB); + $mergeStmt->bindValue(4, time() + $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(4, time(), \PDO::PARAM_INT); + $mergeStmt->bindParam(5, $data, \PDO::PARAM_LOB); + $mergeStmt->bindValue(6, time() + $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(6, time(), \PDO::PARAM_INT); + } else { + $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $mergeStmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT); + } + + return $mergeStmt; + } + + /** + * Return a PDO instance. + * + * @return \PDO + */ + protected function getConnection() + { + if (null === $this->pdo) { + $this->connect($this->dsn ?: ini_get('session.save_path')); + } + + return $this->pdo; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php new file mode 100644 index 00000000..678fb028 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Predis\Response\ErrorInterface; +use Symfony\Component\Cache\Traits\RedisClusterProxy; +use Symfony\Component\Cache\Traits\RedisProxy; + +/** + * Redis based session storage handler based on the Redis class + * provided by the PHP redis extension. + * + * @author Dalibor Karlović + */ +class RedisSessionHandler extends AbstractSessionHandler +{ + private $redis; + + /** + * @var string Key prefix for shared environments + */ + private $prefix; + + /** + * @var int Time to live in seconds + */ + private $ttl; + + /** + * List of available options: + * * prefix: The prefix to use for the keys in order to avoid collision on the Redis server + * * ttl: The time to live in seconds. + * + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis + * + * @throws \InvalidArgumentException When unsupported client or options are passed + */ + public function __construct($redis, array $options = []) + { + if ( + !$redis instanceof \Redis && + !$redis instanceof \RedisArray && + !$redis instanceof \RedisCluster && + !$redis instanceof \Predis\ClientInterface && + !$redis instanceof RedisProxy && + !$redis instanceof RedisClusterProxy + ) { + throw new \InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, %s given', __METHOD__, \is_object($redis) ? \get_class($redis) : \gettype($redis))); + } + + if ($diff = array_diff(array_keys($options), ['prefix', 'ttl'])) { + throw new \InvalidArgumentException(sprintf('The following options are not supported "%s"', implode(', ', $diff))); + } + + $this->redis = $redis; + $this->prefix = $options['prefix'] ?? 'sf_s'; + $this->ttl = $options['ttl'] ?? (int) ini_get('session.gc_maxlifetime'); + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId): string + { + return $this->redis->get($this->prefix.$sessionId) ?: ''; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data): bool + { + $result = $this->redis->setEx($this->prefix.$sessionId, $this->ttl, $data); + + return $result && !$result instanceof ErrorInterface; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId): bool + { + $this->redis->del($this->prefix.$sessionId); + + return true; + } + + /** + * {@inheritdoc} + */ + public function close(): bool + { + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime): bool + { + return true; + } + + /** + * @return bool + */ + public function updateTimestamp($sessionId, $data) + { + return (bool) $this->redis->expire($this->prefix.$sessionId, $this->ttl); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php new file mode 100644 index 00000000..f4feeac0 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Doctrine\DBAL\DriverManager; +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Traits\RedisClusterProxy; +use Symfony\Component\Cache\Traits\RedisProxy; + +/** + * @author Nicolas Grekas + */ +class SessionHandlerFactory +{ + /** + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy|\Memcached|\PDO|string $connection Connection or DSN + */ + public static function createHandler($connection): AbstractSessionHandler + { + if (!\is_string($connection) && !\is_object($connection)) { + throw new \TypeError(sprintf('Argument 1 passed to %s() must be a string or a connection object, %s given.', __METHOD__, \gettype($connection))); + } + + switch (true) { + case $connection instanceof \Redis: + case $connection instanceof \RedisArray: + case $connection instanceof \RedisCluster: + case $connection instanceof \Predis\ClientInterface: + case $connection instanceof RedisProxy: + case $connection instanceof RedisClusterProxy: + return new RedisSessionHandler($connection); + + case $connection instanceof \Memcached: + return new MemcachedSessionHandler($connection); + + case $connection instanceof \PDO: + return new PdoSessionHandler($connection); + + case !\is_string($connection): + throw new \InvalidArgumentException(sprintf('Unsupported Connection: %s.', \get_class($connection))); + case 0 === strpos($connection, 'file://'): + return new StrictSessionHandler(new NativeFileSessionHandler(substr($connection, 7))); + + case 0 === strpos($connection, 'redis://'): + case 0 === strpos($connection, 'rediss://'): + case 0 === strpos($connection, 'memcached://'): + if (!class_exists(AbstractAdapter::class)) { + throw new InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection)); + } + $handlerClass = 0 === strpos($connection, 'memcached://') ? MemcachedSessionHandler::class : RedisSessionHandler::class; + $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]); + + return new $handlerClass($connection); + + case 0 === strpos($connection, 'pdo_oci://'): + if (!class_exists(DriverManager::class)) { + throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require doctrine/dbal".', $connection)); + } + $connection = DriverManager::getConnection(['url' => $connection])->getWrappedConnection(); + // no break; + + case 0 === strpos($connection, 'mssql://'): + case 0 === strpos($connection, 'mysql://'): + case 0 === strpos($connection, 'mysql2://'): + case 0 === strpos($connection, 'pgsql://'): + case 0 === strpos($connection, 'postgres://'): + case 0 === strpos($connection, 'postgresql://'): + case 0 === strpos($connection, 'sqlsrv://'): + case 0 === strpos($connection, 'sqlite://'): + case 0 === strpos($connection, 'sqlite3://'): + return new PdoSessionHandler($connection); + } + + throw new \InvalidArgumentException(sprintf('Unsupported Connection: %s.', $connection)); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php new file mode 100644 index 00000000..4292a3b2 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Adds basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`. + * + * @author Nicolas Grekas + */ +class StrictSessionHandler extends AbstractSessionHandler +{ + private $handler; + private $doDestroy; + + public function __construct(\SessionHandlerInterface $handler) + { + if ($handler instanceof \SessionUpdateTimestampHandlerInterface) { + throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', \get_class($handler), self::class)); + } + + $this->handler = $handler; + } + + /** + * @return bool + */ + public function open($savePath, $sessionName) + { + parent::open($savePath, $sessionName); + + return $this->handler->open($savePath, $sessionName); + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId) + { + return $this->handler->read($sessionId); + } + + /** + * @return bool + */ + public function updateTimestamp($sessionId, $data) + { + return $this->write($sessionId, $data); + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data) + { + return $this->handler->write($sessionId, $data); + } + + /** + * @return bool + */ + public function destroy($sessionId) + { + $this->doDestroy = true; + $destroyed = parent::destroy($sessionId); + + return $this->doDestroy ? $this->doDestroy($sessionId) : $destroyed; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId) + { + $this->doDestroy = false; + + return $this->handler->destroy($sessionId); + } + + /** + * @return bool + */ + public function close() + { + return $this->handler->close(); + } + + /** + * @return bool + */ + public function gc($maxlifetime) + { + return $this->handler->gc($maxlifetime); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MetadataBag.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MetadataBag.php new file mode 100644 index 00000000..c79ee023 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MetadataBag.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Metadata container. + * + * Adds metadata to the session. + * + * @author Drak + */ +class MetadataBag implements SessionBagInterface +{ + const CREATED = 'c'; + const UPDATED = 'u'; + const LIFETIME = 'l'; + + /** + * @var string + */ + private $name = '__metadata'; + + /** + * @var string + */ + private $storageKey; + + /** + * @var array + */ + protected $meta = [self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0]; + + /** + * Unix timestamp. + * + * @var int + */ + private $lastUsed; + + /** + * @var int + */ + private $updateThreshold; + + /** + * @param string $storageKey The key used to store bag in the session + * @param int $updateThreshold The time to wait between two UPDATED updates + */ + public function __construct(string $storageKey = '_sf2_meta', int $updateThreshold = 0) + { + $this->storageKey = $storageKey; + $this->updateThreshold = $updateThreshold; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$array) + { + $this->meta = &$array; + + if (isset($array[self::CREATED])) { + $this->lastUsed = $this->meta[self::UPDATED]; + + $timeStamp = time(); + if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) { + $this->meta[self::UPDATED] = $timeStamp; + } + } else { + $this->stampCreated(); + } + } + + /** + * Gets the lifetime that the session cookie was set with. + * + * @return int + */ + public function getLifetime() + { + return $this->meta[self::LIFETIME]; + } + + /** + * Stamps a new session's metadata. + * + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + */ + public function stampNew(int $lifetime = null) + { + $this->stampCreated($lifetime); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * Gets the created timestamp metadata. + * + * @return int Unix timestamp + */ + public function getCreated() + { + return $this->meta[self::CREATED]; + } + + /** + * Gets the last used metadata. + * + * @return int Unix timestamp + */ + public function getLastUsed() + { + return $this->lastUsed; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // nothing to do + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * Sets name. + */ + public function setName(string $name) + { + $this->name = $name; + } + + private function stampCreated(int $lifetime = null): void + { + $timeStamp = time(); + $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; + $this->meta[self::LIFETIME] = (null === $lifetime) ? ini_get('session.cookie_lifetime') : $lifetime; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php new file mode 100644 index 00000000..4662bd86 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php @@ -0,0 +1,252 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * MockArraySessionStorage mocks the session for unit tests. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle. + * + * When doing functional testing, you should use MockFileSessionStorage instead. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Drak + */ +class MockArraySessionStorage implements SessionStorageInterface +{ + /** + * @var string + */ + protected $id = ''; + + /** + * @var string + */ + protected $name; + + /** + * @var bool + */ + protected $started = false; + + /** + * @var bool + */ + protected $closed = false; + + /** + * @var array + */ + protected $data = []; + + /** + * @var MetadataBag + */ + protected $metadataBag; + + /** + * @var array|SessionBagInterface[] + */ + protected $bags = []; + + public function __construct(string $name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + $this->name = $name; + $this->setMetadataBag($metaBag); + } + + public function setSessionData(array $array) + { + $this->data = $array; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (empty($this->id)) { + $this->id = $this->generateId(); + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate(bool $destroy = false, int $lifetime = null) + { + if (!$this->started) { + $this->start(); + } + + $this->metadataBag->stampNew($lifetime); + $this->id = $this->generateId(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function setId(string $id) + { + if ($this->started) { + throw new \LogicException('Cannot set session ID after the session has started.'); + } + + $this->id = $id; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function setName(string $name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function save() + { + if (!$this->started || $this->closed) { + throw new \RuntimeException('Trying to save a session that was not started yet or was already closed'); + } + // nothing to do since we don't persist the session data + $this->closed = false; + $this->started = false; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $this->data = []; + + // reconnect the bags to the session + $this->loadSession(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->bags[$bag->getName()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag(string $name) + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name)); + } + + if (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->started; + } + + public function setMetadataBag(MetadataBag $bag = null) + { + if (null === $bag) { + $bag = new MetadataBag(); + } + + $this->metadataBag = $bag; + } + + /** + * Gets the MetadataBag. + * + * @return MetadataBag + */ + public function getMetadataBag() + { + return $this->metadataBag; + } + + /** + * Generates a session ID. + * + * This doesn't need to be particularly cryptographically secure since this is just + * a mock. + * + * @return string + */ + protected function generateId() + { + return hash('sha256', uniqid('ss_mock_', true)); + } + + protected function loadSession() + { + $bags = array_merge($this->bags, [$this->metadataBag]); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : []; + $bag->initialize($this->data[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php new file mode 100644 index 00000000..019a6348 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +/** + * MockFileSessionStorage is used to mock sessions for + * functional testing when done in a single PHP process. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle and this class does + * not pollute any session related globals, including session_*() functions + * or session.* PHP ini directives. + * + * @author Drak + */ +class MockFileSessionStorage extends MockArraySessionStorage +{ + private $savePath; + + /** + * @param string $savePath Path of directory to save session files + */ + public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + if (null === $savePath) { + $savePath = sys_get_temp_dir(); + } + + if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) { + throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $savePath)); + } + + $this->savePath = $savePath; + + parent::__construct($name, $metaBag); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (!$this->id) { + $this->id = $this->generateId(); + } + + $this->read(); + + $this->started = true; + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate(bool $destroy = false, int $lifetime = null) + { + if (!$this->started) { + $this->start(); + } + + if ($destroy) { + $this->destroy(); + } + + return parent::regenerate($destroy, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function save() + { + if (!$this->started) { + throw new \RuntimeException('Trying to save a session that was not started yet or was already closed'); + } + + $data = $this->data; + + foreach ($this->bags as $bag) { + if (empty($data[$key = $bag->getStorageKey()])) { + unset($data[$key]); + } + } + if ([$key = $this->metadataBag->getStorageKey()] === array_keys($data)) { + unset($data[$key]); + } + + try { + if ($data) { + file_put_contents($this->getFilePath(), serialize($data)); + } else { + $this->destroy(); + } + } finally { + $this->data = $data; + } + + // this is needed for Silex, where the session object is re-used across requests + // in functional tests. In Symfony, the container is rebooted, so we don't have + // this issue + $this->started = false; + } + + /** + * Deletes a session from persistent storage. + * Deliberately leaves session data in memory intact. + */ + private function destroy(): void + { + if (is_file($this->getFilePath())) { + unlink($this->getFilePath()); + } + } + + /** + * Calculate path to file. + */ + private function getFilePath(): string + { + return $this->savePath.'/'.$this->id.'.mocksess'; + } + + /** + * Reads session from storage and loads session. + */ + private function read(): void + { + $filePath = $this->getFilePath(); + $this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : []; + + $this->loadSession(); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/NativeSessionStorage.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/NativeSessionStorage.php new file mode 100644 index 00000000..d3b44c85 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/NativeSessionStorage.php @@ -0,0 +1,466 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\SessionUtils; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; + +/** + * This provides a base class for session attribute storage. + * + * @author Drak + */ +class NativeSessionStorage implements SessionStorageInterface +{ + /** + * @var SessionBagInterface[] + */ + protected $bags = []; + + /** + * @var bool + */ + protected $started = false; + + /** + * @var bool + */ + protected $closed = false; + + /** + * @var AbstractProxy|\SessionHandlerInterface + */ + protected $saveHandler; + + /** + * @var MetadataBag + */ + protected $metadataBag; + + /** + * @var string|null + */ + private $emulateSameSite; + + /** + * Depending on how you want the storage driver to behave you probably + * want to override this constructor entirely. + * + * List of options for $options array with their defaults. + * + * @see https://php.net/session.configuration for options + * but we omit 'session.' from the beginning of the keys for convenience. + * + * ("auto_start", is not supported as it tells PHP to start a session before + * PHP starts to execute user-land code. Setting during runtime has no effect). + * + * cache_limiter, "" (use "0" to prevent headers from being sent entirely). + * cache_expire, "0" + * cookie_domain, "" + * cookie_httponly, "" + * cookie_lifetime, "0" + * cookie_path, "/" + * cookie_secure, "" + * cookie_samesite, null + * gc_divisor, "100" + * gc_maxlifetime, "1440" + * gc_probability, "1" + * lazy_write, "1" + * name, "PHPSESSID" + * referer_check, "" + * serialize_handler, "php" + * use_strict_mode, "0" + * use_cookies, "1" + * use_only_cookies, "1" + * use_trans_sid, "0" + * upload_progress.enabled, "1" + * upload_progress.cleanup, "1" + * upload_progress.prefix, "upload_progress_" + * upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS" + * upload_progress.freq, "1%" + * upload_progress.min-freq, "1" + * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset=" + * sid_length, "32" + * sid_bits_per_character, "5" + * trans_sid_hosts, $_SERVER['HTTP_HOST'] + * trans_sid_tags, "a=href,area=href,frame=src,form=" + * + * @param AbstractProxy|\SessionHandlerInterface|null $handler + */ + public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null) + { + if (!\extension_loaded('session')) { + throw new \LogicException('PHP extension "session" is required.'); + } + + $options += [ + 'cache_limiter' => '', + 'cache_expire' => 0, + 'use_cookies' => 1, + 'lazy_write' => 1, + 'use_strict_mode' => 1, + ]; + + session_register_shutdown(); + + $this->setMetadataBag($metaBag); + $this->setOptions($options); + $this->setSaveHandler($handler); + } + + /** + * Gets the save handler instance. + * + * @return AbstractProxy|\SessionHandlerInterface + */ + public function getSaveHandler() + { + return $this->saveHandler; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (\PHP_SESSION_ACTIVE === session_status()) { + throw new \RuntimeException('Failed to start the session: already started by PHP.'); + } + + if (filter_var(ini_get('session.use_cookies'), FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) { + throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line)); + } + + // ok to try and start the session + if (!session_start()) { + throw new \RuntimeException('Failed to start the session'); + } + + if (null !== $this->emulateSameSite) { + $originalCookie = SessionUtils::popSessionCookie(session_name(), session_id()); + if (null !== $originalCookie) { + header(sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), false); + } + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->saveHandler->getId(); + } + + /** + * {@inheritdoc} + */ + public function setId(string $id) + { + $this->saveHandler->setId($id); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->saveHandler->getName(); + } + + /** + * {@inheritdoc} + */ + public function setName(string $name) + { + $this->saveHandler->setName($name); + } + + /** + * {@inheritdoc} + */ + public function regenerate(bool $destroy = false, int $lifetime = null) + { + // Cannot regenerate the session ID for non-active sessions. + if (\PHP_SESSION_ACTIVE !== session_status()) { + return false; + } + + if (headers_sent()) { + return false; + } + + if (null !== $lifetime) { + ini_set('session.cookie_lifetime', $lifetime); + } + + if ($destroy) { + $this->metadataBag->stampNew(); + } + + $isRegenerated = session_regenerate_id($destroy); + + // The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it. + // @see https://bugs.php.net/70013 + $this->loadSession(); + + if (null !== $this->emulateSameSite) { + $originalCookie = SessionUtils::popSessionCookie(session_name(), session_id()); + if (null !== $originalCookie) { + header(sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), false); + } + } + + return $isRegenerated; + } + + /** + * {@inheritdoc} + */ + public function save() + { + // Store a copy so we can restore the bags in case the session was not left empty + $session = $_SESSION; + + foreach ($this->bags as $bag) { + if (empty($_SESSION[$key = $bag->getStorageKey()])) { + unset($_SESSION[$key]); + } + } + if ([$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) { + unset($_SESSION[$key]); + } + + // Register error handler to add information about the current save handler + $previousHandler = set_error_handler(function ($type, $msg, $file, $line) use (&$previousHandler) { + if (E_WARNING === $type && 0 === strpos($msg, 'session_write_close():')) { + $handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler; + $msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', \get_class($handler)); + } + + return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false; + }); + + try { + session_write_close(); + } finally { + restore_error_handler(); + + // Restore only if not empty + if ($_SESSION) { + $_SESSION = $session; + } + } + + $this->closed = true; + $this->started = false; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $_SESSION = []; + + // reconnect the bags to the session + $this->loadSession(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + if ($this->started) { + throw new \LogicException('Cannot register a bag when the session is already started.'); + } + + $this->bags[$bag->getName()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag(string $name) + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name)); + } + + if (!$this->started && $this->saveHandler->isActive()) { + $this->loadSession(); + } elseif (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + public function setMetadataBag(MetadataBag $metaBag = null) + { + if (null === $metaBag) { + $metaBag = new MetadataBag(); + } + + $this->metadataBag = $metaBag; + } + + /** + * Gets the MetadataBag. + * + * @return MetadataBag + */ + public function getMetadataBag() + { + return $this->metadataBag; + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->started; + } + + /** + * Sets session.* ini variables. + * + * For convenience we omit 'session.' from the beginning of the keys. + * Explicitly ignores other ini keys. + * + * @param array $options Session ini directives [key => value] + * + * @see https://php.net/session.configuration + */ + public function setOptions(array $options) + { + if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) { + return; + } + + $validOptions = array_flip([ + 'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly', + 'cookie_lifetime', 'cookie_path', 'cookie_secure', 'cookie_samesite', + 'gc_divisor', 'gc_maxlifetime', 'gc_probability', + 'lazy_write', 'name', 'referer_check', + 'serialize_handler', 'use_strict_mode', 'use_cookies', + 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', + 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', + 'upload_progress.freq', 'upload_progress.min_freq', 'url_rewriter.tags', + 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags', + ]); + + foreach ($options as $key => $value) { + if (isset($validOptions[$key])) { + if ('cookie_samesite' === $key && \PHP_VERSION_ID < 70300) { + // PHP < 7.3 does not support same_site cookies. We will emulate it in + // the start() method instead. + $this->emulateSameSite = $value; + continue; + } + ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value); + } + } + } + + /** + * Registers session save handler as a PHP session handler. + * + * To use internal PHP session save handlers, override this method using ini_set with + * session.save_handler and session.save_path e.g. + * + * ini_set('session.save_handler', 'files'); + * ini_set('session.save_path', '/tmp'); + * + * or pass in a \SessionHandler instance which configures session.save_handler in the + * constructor, for a template see NativeFileSessionHandler or use handlers in + * composer package drak/native-session + * + * @see https://php.net/session-set-save-handler + * @see https://php.net/sessionhandlerinterface + * @see https://php.net/sessionhandler + * @see https://github.com/zikula/NativeSession + * + * @param AbstractProxy|\SessionHandlerInterface|null $saveHandler + * + * @throws \InvalidArgumentException + */ + public function setSaveHandler($saveHandler = null) + { + if (!$saveHandler instanceof AbstractProxy && + !$saveHandler instanceof \SessionHandlerInterface && + null !== $saveHandler) { + throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.'); + } + + // Wrap $saveHandler in proxy and prevent double wrapping of proxy + if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) { + $saveHandler = new SessionHandlerProxy($saveHandler); + } elseif (!$saveHandler instanceof AbstractProxy) { + $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler())); + } + $this->saveHandler = $saveHandler; + + if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) { + return; + } + + if ($this->saveHandler instanceof SessionHandlerProxy) { + session_set_save_handler($this->saveHandler, false); + } + } + + /** + * Load the session with attributes. + * + * After starting the session, PHP retrieves the session from whatever handlers + * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()). + * PHP takes the return value from the read() handler, unserializes it + * and populates $_SESSION with the result automatically. + */ + protected function loadSession(array &$session = null) + { + if (null === $session) { + $session = &$_SESSION; + } + + $bags = array_merge($this->bags, [$this->metadataBag]); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $session[$key] = isset($session[$key]) && \is_array($session[$key]) ? $session[$key] : []; + $bag->initialize($session[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php new file mode 100644 index 00000000..72dbef13 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; + +/** + * Allows session to be started by PHP and managed by Symfony. + * + * @author Drak + */ +class PhpBridgeSessionStorage extends NativeSessionStorage +{ + /** + * @param AbstractProxy|\SessionHandlerInterface|null $handler + */ + public function __construct($handler = null, MetadataBag $metaBag = null) + { + if (!\extension_loaded('session')) { + throw new \LogicException('PHP extension "session" is required.'); + } + + $this->setMetadataBag($metaBag); + $this->setSaveHandler($handler); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags and nothing else that may be set + // since the purpose of this driver is to share a handler + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // reconnect the bags to the session + $this->loadSession(); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php new file mode 100644 index 00000000..ded79d64 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +/** + * @author Drak + */ +abstract class AbstractProxy +{ + /** + * Flag if handler wraps an internal PHP session handler (using \SessionHandler). + * + * @var bool + */ + protected $wrapper = false; + + /** + * @var string + */ + protected $saveHandlerName; + + /** + * Gets the session.save_handler name. + * + * @return string|null + */ + public function getSaveHandlerName() + { + return $this->saveHandlerName; + } + + /** + * Is this proxy handler and instance of \SessionHandlerInterface. + * + * @return bool + */ + public function isSessionHandlerInterface() + { + return $this instanceof \SessionHandlerInterface; + } + + /** + * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. + * + * @return bool + */ + public function isWrapper() + { + return $this->wrapper; + } + + /** + * Has a session started? + * + * @return bool + */ + public function isActive() + { + return \PHP_SESSION_ACTIVE === session_status(); + } + + /** + * Gets the session ID. + * + * @return string + */ + public function getId() + { + return session_id(); + } + + /** + * Sets the session ID. + * + * @throws \LogicException + */ + public function setId(string $id) + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the ID of an active session'); + } + + session_id($id); + } + + /** + * Gets the session name. + * + * @return string + */ + public function getName() + { + return session_name(); + } + + /** + * Sets the session name. + * + * @throws \LogicException + */ + public function setName(string $name) + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the name of an active session'); + } + + session_name($name); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php new file mode 100644 index 00000000..de4f550b --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +/** + * @author Drak + */ +class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + protected $handler; + + public function __construct(\SessionHandlerInterface $handler) + { + $this->handler = $handler; + $this->wrapper = ($handler instanceof \SessionHandler); + $this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user'; + } + + /** + * @return \SessionHandlerInterface + */ + public function getHandler() + { + return $this->handler; + } + + // \SessionHandlerInterface + + /** + * @return bool + */ + public function open($savePath, $sessionName) + { + return (bool) $this->handler->open($savePath, $sessionName); + } + + /** + * @return bool + */ + public function close() + { + return (bool) $this->handler->close(); + } + + /** + * @return string + */ + public function read($sessionId) + { + return (string) $this->handler->read($sessionId); + } + + /** + * @return bool + */ + public function write($sessionId, $data) + { + return (bool) $this->handler->write($sessionId, $data); + } + + /** + * @return bool + */ + public function destroy($sessionId) + { + return (bool) $this->handler->destroy($sessionId); + } + + /** + * @return bool + */ + public function gc($maxlifetime) + { + return (bool) $this->handler->gc($maxlifetime); + } + + /** + * @return bool + */ + public function validateId($sessionId) + { + return !$this->handler instanceof \SessionUpdateTimestampHandlerInterface || $this->handler->validateId($sessionId); + } + + /** + * @return bool + */ + public function updateTimestamp($sessionId, $data) + { + return $this->handler instanceof \SessionUpdateTimestampHandlerInterface ? $this->handler->updateTimestamp($sessionId, $data) : $this->write($sessionId, $data); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/SessionStorageInterface.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/SessionStorageInterface.php new file mode 100644 index 00000000..eb8e8ff2 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/SessionStorageInterface.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * StorageInterface. + * + * @author Fabien Potencier + * @author Drak + */ +interface SessionStorageInterface +{ + /** + * Starts the session. + * + * @return bool True if started + * + * @throws \RuntimeException if something goes wrong starting the session + */ + public function start(); + + /** + * Checks if the session is started. + * + * @return bool True if started, false otherwise + */ + public function isStarted(); + + /** + * Returns the session ID. + * + * @return string The session ID or empty + */ + public function getId(); + + /** + * Sets the session ID. + */ + public function setId(string $id); + + /** + * Returns the session name. + * + * @return string The session name + */ + public function getName(); + + /** + * Sets the session name. + */ + public function setName(string $name); + + /** + * Regenerates id that represents this storage. + * + * This method must invoke session_regenerate_id($destroy) unless + * this interface is used for a storage object designed for unit + * or functional testing where a real PHP session would interfere + * with testing. + * + * Note regenerate+destroy should not clear the session data in memory + * only delete the session data from persistent storage. + * + * Care: When regenerating the session ID no locking is involved in PHP's + * session design. See https://bugs.php.net/61470 for a discussion. + * So you must make sure the regenerated session is saved BEFORE sending the + * headers with the new ID. Symfony's HttpKernel offers a listener for this. + * See Symfony\Component\HttpKernel\EventListener\SaveSessionListener. + * Otherwise session data could get lost again for concurrent requests with the + * new ID. One result could be that you get logged out after just logging in. + * + * @param bool $destroy Destroy session when regenerating? + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool True if session regenerated, false if error + * + * @throws \RuntimeException If an error occurs while regenerating this storage + */ + public function regenerate(bool $destroy = false, int $lifetime = null); + + /** + * Force the session to be saved and closed. + * + * This method must invoke session_write_close() unless this interface is + * used for a storage object design for unit or functional testing where + * a real PHP session would interfere with testing, in which case + * it should actually persist the session data if required. + * + * @throws \RuntimeException if the session is saved without being started, or if the session + * is already closed + */ + public function save(); + + /** + * Clear all session data in memory. + */ + public function clear(); + + /** + * Gets a SessionBagInterface by name. + * + * @return SessionBagInterface + * + * @throws \InvalidArgumentException If the bag does not exist + */ + public function getBag(string $name); + + /** + * Registers a SessionBagInterface for use. + */ + public function registerBag(SessionBagInterface $bag); + + /** + * @return MetadataBag + */ + public function getMetadataBag(); +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/StreamedResponse.php b/lam/lib/3rdParty/composer/symfony/http-foundation/StreamedResponse.php new file mode 100644 index 00000000..65ec2d98 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/StreamedResponse.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * StreamedResponse represents a streamed HTTP response. + * + * A StreamedResponse uses a callback for its content. + * + * The callback should use the standard PHP functions like echo + * to stream the response back to the client. The flush() function + * can also be used if needed. + * + * @see flush() + * + * @author Fabien Potencier + */ +class StreamedResponse extends Response +{ + protected $callback; + protected $streamed; + private $headersSent; + + public function __construct(callable $callback = null, int $status = 200, array $headers = []) + { + parent::__construct(null, $status, $headers); + + if (null !== $callback) { + $this->setCallback($callback); + } + $this->streamed = false; + $this->headersSent = false; + } + + /** + * Factory method for chainability. + * + * @param callable|null $callback A valid PHP callback or null to set it later + * + * @return static + */ + public static function create($callback = null, int $status = 200, array $headers = []) + { + return new static($callback, $status, $headers); + } + + /** + * Sets the PHP callback associated with this Response. + * + * @return $this + */ + public function setCallback(callable $callback) + { + $this->callback = $callback; + + return $this; + } + + /** + * {@inheritdoc} + * + * This method only sends the headers once. + * + * @return $this + */ + public function sendHeaders() + { + if ($this->headersSent) { + return $this; + } + + $this->headersSent = true; + + return parent::sendHeaders(); + } + + /** + * {@inheritdoc} + * + * This method only sends the content once. + * + * @return $this + */ + public function sendContent() + { + if ($this->streamed) { + return $this; + } + + $this->streamed = true; + + if (null === $this->callback) { + throw new \LogicException('The Response callback must not be null.'); + } + + ($this->callback)(); + + return $this; + } + + /** + * {@inheritdoc} + * + * @throws \LogicException when the content is not null + * + * @return $this + */ + public function setContent(?string $content) + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a StreamedResponse instance.'); + } + + $this->streamed = true; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getContent() + { + return false; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php new file mode 100644 index 00000000..cb216ea1 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Request; + +final class RequestAttributeValueSame extends Constraint +{ + private $name; + private $value; + + public function __construct(string $name, string $value) + { + $this->name = $name; + $this->value = $value; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('has attribute "%s" with value "%s"', $this->name, $this->value); + } + + /** + * @param Request $request + * + * {@inheritdoc} + */ + protected function matches($request): bool + { + return $this->value === $request->attributes->get($this->name); + } + + /** + * @param Request $request + * + * {@inheritdoc} + */ + protected function failureDescription($request): string + { + return 'the Request '.$this->toString(); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php new file mode 100644 index 00000000..554e1a16 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseCookieValueSame extends Constraint +{ + private $name; + private $value; + private $path; + private $domain; + + public function __construct(string $name, string $value, string $path = '/', string $domain = null) + { + $this->name = $name; + $this->value = $value; + $this->path = $path; + $this->domain = $domain; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + $str = sprintf('has cookie "%s"', $this->name); + if ('/' !== $this->path) { + $str .= sprintf(' with path "%s"', $this->path); + } + if ($this->domain) { + $str .= sprintf(' for domain "%s"', $this->domain); + } + $str .= sprintf(' with value "%s"', $this->value); + + return $str; + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + $cookie = $this->getCookie($response); + if (!$cookie) { + return false; + } + + return $this->value === $cookie->getValue(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + protected function getCookie(Response $response): ?Cookie + { + $cookies = $response->headers->getCookies(); + + $filteredCookies = array_filter($cookies, function (Cookie $cookie) { + return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain; + }); + + return reset($filteredCookies) ?: null; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php new file mode 100644 index 00000000..eae9e271 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseHasCookie extends Constraint +{ + private $name; + private $path; + private $domain; + + public function __construct(string $name, string $path = '/', string $domain = null) + { + $this->name = $name; + $this->path = $path; + $this->domain = $domain; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + $str = sprintf('has cookie "%s"', $this->name); + if ('/' !== $this->path) { + $str .= sprintf(' with path "%s"', $this->path); + } + if ($this->domain) { + $str .= sprintf(' for domain "%s"', $this->domain); + } + + return $str; + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return null !== $this->getCookie($response); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + private function getCookie(Response $response): ?Cookie + { + $cookies = $response->headers->getCookies(); + + $filteredCookies = array_filter($cookies, function (Cookie $cookie) { + return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain; + }); + + return reset($filteredCookies) ?: null; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php new file mode 100644 index 00000000..68ad8273 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseHasHeader extends Constraint +{ + private $headerName; + + public function __construct(string $headerName) + { + $this->headerName = $headerName; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('has header "%s"', $this->headerName); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $response->headers->has($this->headerName); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php new file mode 100644 index 00000000..a27d0c73 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseHeaderSame extends Constraint +{ + private $headerName; + private $expectedValue; + + public function __construct(string $headerName, string $expectedValue) + { + $this->headerName = $headerName; + $this->expectedValue = $expectedValue; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $this->expectedValue === $response->headers->get($this->headerName, null); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php new file mode 100644 index 00000000..8c4b883f --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseIsRedirected extends Constraint +{ + /** + * {@inheritdoc} + */ + public function toString(): string + { + return 'is redirected'; + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $response->isRedirect(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function additionalFailureDescription($response): string + { + return (string) $response; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php new file mode 100644 index 00000000..9c665589 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseIsSuccessful extends Constraint +{ + /** + * {@inheritdoc} + */ + public function toString(): string + { + return 'is successful'; + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $response->isSuccessful(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function additionalFailureDescription($response): string + { + return (string) $response; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php new file mode 100644 index 00000000..72bb000b --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseStatusCodeSame extends Constraint +{ + private $statusCode; + + public function __construct(int $statusCode) + { + $this->statusCode = $statusCode; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return 'status code is '.$this->statusCode; + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $this->statusCode === $response->getStatusCode(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function additionalFailureDescription($response): string + { + return (string) $response; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/UrlHelper.php b/lam/lib/3rdParty/composer/symfony/http-foundation/UrlHelper.php new file mode 100644 index 00000000..f114c0a9 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/UrlHelper.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\Routing\RequestContext; + +/** + * A helper service for manipulating URLs within and outside the request scope. + * + * @author Valentin Udaltsov + */ +final class UrlHelper +{ + private $requestStack; + private $requestContext; + + public function __construct(RequestStack $requestStack, RequestContext $requestContext = null) + { + $this->requestStack = $requestStack; + $this->requestContext = $requestContext; + } + + public function getAbsoluteUrl(string $path): string + { + if (false !== strpos($path, '://') || '//' === substr($path, 0, 2)) { + return $path; + } + + if (null === $request = $this->requestStack->getMasterRequest()) { + return $this->getAbsoluteUrlFromContext($path); + } + + if ('#' === $path[0]) { + $path = $request->getRequestUri().$path; + } elseif ('?' === $path[0]) { + $path = $request->getPathInfo().$path; + } + + if (!$path || '/' !== $path[0]) { + $prefix = $request->getPathInfo(); + $last = \strlen($prefix) - 1; + if ($last !== $pos = strrpos($prefix, '/')) { + $prefix = substr($prefix, 0, $pos).'/'; + } + + return $request->getUriForPath($prefix.$path); + } + + return $request->getSchemeAndHttpHost().$path; + } + + public function getRelativePath(string $path): string + { + if (false !== strpos($path, '://') || '//' === substr($path, 0, 2)) { + return $path; + } + + if (null === $request = $this->requestStack->getMasterRequest()) { + return $path; + } + + return $request->getRelativeUriForPath($path); + } + + private function getAbsoluteUrlFromContext(string $path): string + { + if (null === $this->requestContext || '' === $host = $this->requestContext->getHost()) { + return $path; + } + + $scheme = $this->requestContext->getScheme(); + $port = ''; + + if ('http' === $scheme && 80 !== $this->requestContext->getHttpPort()) { + $port = ':'.$this->requestContext->getHttpPort(); + } elseif ('https' === $scheme && 443 !== $this->requestContext->getHttpsPort()) { + $port = ':'.$this->requestContext->getHttpsPort(); + } + + if ('#' === $path[0]) { + $queryString = $this->requestContext->getQueryString(); + $path = $this->requestContext->getPathInfo().($queryString ? '?'.$queryString : '').$path; + } elseif ('?' === $path[0]) { + $path = $this->requestContext->getPathInfo().$path; + } + + if ('/' !== $path[0]) { + $path = rtrim($this->requestContext->getBaseUrl(), '/').'/'.$path; + } + + return $scheme.'://'.$host.$port.$path; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/http-foundation/composer.json b/lam/lib/3rdParty/composer/symfony/http-foundation/composer.json new file mode 100644 index 00000000..fcec68c3 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/http-foundation/composer.json @@ -0,0 +1,39 @@ +{ + "name": "symfony/http-foundation", + "type": "library", + "description": "Symfony HttpFoundation Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^7.2.5", + "symfony/mime": "^4.4|^5.0", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/expression-language": "^4.4|^5.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/.gitattributes b/lam/lib/3rdParty/composer/symfony/mime/.gitattributes new file mode 100644 index 00000000..ebb92870 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/.gitattributes @@ -0,0 +1,3 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitignore export-ignore diff --git a/lam/lib/3rdParty/composer/symfony/mime/Address.php b/lam/lib/3rdParty/composer/symfony/mime/Address.php new file mode 100644 index 00000000..b0dcbd08 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Address.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Egulias\EmailValidator\EmailValidator; +use Egulias\EmailValidator\Validation\RFCValidation; +use Symfony\Component\Mime\Encoder\IdnAddressEncoder; +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Exception\LogicException; +use Symfony\Component\Mime\Exception\RfcComplianceException; + +/** + * @author Fabien Potencier + */ +final class Address +{ + /** + * A regex that matches a structure like 'Name '. + * It matches anything between the first < and last > as email address. + * This allows to use a single string to construct an Address, which can be convenient to use in + * config, and allows to have more readable config. + * This does not try to cover all edge cases for address. + */ + private const FROM_STRING_PATTERN = '~(?[^<]*)<(?.*)>[^>]*~'; + + private static $validator; + private static $encoder; + + private $address; + private $name; + + public function __construct(string $address, string $name = '') + { + if (!class_exists(EmailValidator::class)) { + throw new LogicException(sprintf('The "%s" class cannot be used as it needs "%s"; try running "composer require egulias/email-validator".', __CLASS__, EmailValidator::class)); + } + + if (null === self::$validator) { + self::$validator = new EmailValidator(); + } + + $this->address = trim($address); + $this->name = trim(str_replace(["\n", "\r"], '', $name)); + + if (!self::$validator->isValid($this->address, new RFCValidation())) { + throw new RfcComplianceException(sprintf('Email "%s" does not comply with addr-spec of RFC 2822.', $address)); + } + } + + public function getAddress(): string + { + return $this->address; + } + + public function getName(): string + { + return $this->name; + } + + public function getEncodedAddress(): string + { + if (null === self::$encoder) { + self::$encoder = new IdnAddressEncoder(); + } + + return self::$encoder->encodeString($this->address); + } + + public function toString(): string + { + return ($n = $this->getName()) ? $n.' <'.$this->getEncodedAddress().'>' : $this->getEncodedAddress(); + } + + /** + * @param Address|string $address + */ + public static function create($address): self + { + if ($address instanceof self) { + return $address; + } + if (\is_string($address)) { + return new self($address); + } + + throw new InvalidArgumentException(sprintf('An address can be an instance of Address or a string ("%s") given).', \is_object($address) ? \get_class($address) : \gettype($address))); + } + + /** + * @param (Address|string)[] $addresses + * + * @return Address[] + */ + public static function createArray(array $addresses): array + { + $addrs = []; + foreach ($addresses as $address) { + $addrs[] = self::create($address); + } + + return $addrs; + } + + public static function fromString(string $string): self + { + if (false === strpos($string, '<')) { + return new self($string, ''); + } + + if (!preg_match(self::FROM_STRING_PATTERN, $string, $matches)) { + throw new InvalidArgumentException(sprintf('Could not parse "%s" to a "%s" instance.', $string, static::class)); + } + + return new self($matches['addrSpec'], trim($matches['displayName'], ' \'"')); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/BodyRendererInterface.php b/lam/lib/3rdParty/composer/symfony/mime/BodyRendererInterface.php new file mode 100644 index 00000000..d6921726 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/BodyRendererInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +/** + * @author Fabien Potencier + */ +interface BodyRendererInterface +{ + public function render(Message $message): void; +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/CHANGELOG.md b/lam/lib/3rdParty/composer/symfony/mime/CHANGELOG.md new file mode 100644 index 00000000..6148360d --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/CHANGELOG.md @@ -0,0 +1,20 @@ +CHANGELOG +========= + +4.4.0 +----- + + * [BC BREAK] Removed `NamedAddress` (`Address` now supports a name) + * Added PHPUnit constraints + * Added `AbstractPart::asDebugString()` + * Added `Address::fromString()` + +4.3.3 +----- + + * [BC BREAK] Renamed method `Headers::getAll()` to `Headers::all()`. + +4.3.0 +----- + + * Introduced the component as experimental diff --git a/lam/lib/3rdParty/composer/symfony/mime/CharacterStream.php b/lam/lib/3rdParty/composer/symfony/mime/CharacterStream.php new file mode 100644 index 00000000..749066f2 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/CharacterStream.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +/** + * @author Fabien Potencier + * @author Xavier De Cock + * + * @internal + */ +final class CharacterStream +{ + /** Pre-computed for optimization */ + private const UTF8_LENGTH_MAP = [ + "\x00" => 1, "\x01" => 1, "\x02" => 1, "\x03" => 1, "\x04" => 1, "\x05" => 1, "\x06" => 1, "\x07" => 1, + "\x08" => 1, "\x09" => 1, "\x0a" => 1, "\x0b" => 1, "\x0c" => 1, "\x0d" => 1, "\x0e" => 1, "\x0f" => 1, + "\x10" => 1, "\x11" => 1, "\x12" => 1, "\x13" => 1, "\x14" => 1, "\x15" => 1, "\x16" => 1, "\x17" => 1, + "\x18" => 1, "\x19" => 1, "\x1a" => 1, "\x1b" => 1, "\x1c" => 1, "\x1d" => 1, "\x1e" => 1, "\x1f" => 1, + "\x20" => 1, "\x21" => 1, "\x22" => 1, "\x23" => 1, "\x24" => 1, "\x25" => 1, "\x26" => 1, "\x27" => 1, + "\x28" => 1, "\x29" => 1, "\x2a" => 1, "\x2b" => 1, "\x2c" => 1, "\x2d" => 1, "\x2e" => 1, "\x2f" => 1, + "\x30" => 1, "\x31" => 1, "\x32" => 1, "\x33" => 1, "\x34" => 1, "\x35" => 1, "\x36" => 1, "\x37" => 1, + "\x38" => 1, "\x39" => 1, "\x3a" => 1, "\x3b" => 1, "\x3c" => 1, "\x3d" => 1, "\x3e" => 1, "\x3f" => 1, + "\x40" => 1, "\x41" => 1, "\x42" => 1, "\x43" => 1, "\x44" => 1, "\x45" => 1, "\x46" => 1, "\x47" => 1, + "\x48" => 1, "\x49" => 1, "\x4a" => 1, "\x4b" => 1, "\x4c" => 1, "\x4d" => 1, "\x4e" => 1, "\x4f" => 1, + "\x50" => 1, "\x51" => 1, "\x52" => 1, "\x53" => 1, "\x54" => 1, "\x55" => 1, "\x56" => 1, "\x57" => 1, + "\x58" => 1, "\x59" => 1, "\x5a" => 1, "\x5b" => 1, "\x5c" => 1, "\x5d" => 1, "\x5e" => 1, "\x5f" => 1, + "\x60" => 1, "\x61" => 1, "\x62" => 1, "\x63" => 1, "\x64" => 1, "\x65" => 1, "\x66" => 1, "\x67" => 1, + "\x68" => 1, "\x69" => 1, "\x6a" => 1, "\x6b" => 1, "\x6c" => 1, "\x6d" => 1, "\x6e" => 1, "\x6f" => 1, + "\x70" => 1, "\x71" => 1, "\x72" => 1, "\x73" => 1, "\x74" => 1, "\x75" => 1, "\x76" => 1, "\x77" => 1, + "\x78" => 1, "\x79" => 1, "\x7a" => 1, "\x7b" => 1, "\x7c" => 1, "\x7d" => 1, "\x7e" => 1, "\x7f" => 1, + "\x80" => 0, "\x81" => 0, "\x82" => 0, "\x83" => 0, "\x84" => 0, "\x85" => 0, "\x86" => 0, "\x87" => 0, + "\x88" => 0, "\x89" => 0, "\x8a" => 0, "\x8b" => 0, "\x8c" => 0, "\x8d" => 0, "\x8e" => 0, "\x8f" => 0, + "\x90" => 0, "\x91" => 0, "\x92" => 0, "\x93" => 0, "\x94" => 0, "\x95" => 0, "\x96" => 0, "\x97" => 0, + "\x98" => 0, "\x99" => 0, "\x9a" => 0, "\x9b" => 0, "\x9c" => 0, "\x9d" => 0, "\x9e" => 0, "\x9f" => 0, + "\xa0" => 0, "\xa1" => 0, "\xa2" => 0, "\xa3" => 0, "\xa4" => 0, "\xa5" => 0, "\xa6" => 0, "\xa7" => 0, + "\xa8" => 0, "\xa9" => 0, "\xaa" => 0, "\xab" => 0, "\xac" => 0, "\xad" => 0, "\xae" => 0, "\xaf" => 0, + "\xb0" => 0, "\xb1" => 0, "\xb2" => 0, "\xb3" => 0, "\xb4" => 0, "\xb5" => 0, "\xb6" => 0, "\xb7" => 0, + "\xb8" => 0, "\xb9" => 0, "\xba" => 0, "\xbb" => 0, "\xbc" => 0, "\xbd" => 0, "\xbe" => 0, "\xbf" => 0, + "\xc0" => 2, "\xc1" => 2, "\xc2" => 2, "\xc3" => 2, "\xc4" => 2, "\xc5" => 2, "\xc6" => 2, "\xc7" => 2, + "\xc8" => 2, "\xc9" => 2, "\xca" => 2, "\xcb" => 2, "\xcc" => 2, "\xcd" => 2, "\xce" => 2, "\xcf" => 2, + "\xd0" => 2, "\xd1" => 2, "\xd2" => 2, "\xd3" => 2, "\xd4" => 2, "\xd5" => 2, "\xd6" => 2, "\xd7" => 2, + "\xd8" => 2, "\xd9" => 2, "\xda" => 2, "\xdb" => 2, "\xdc" => 2, "\xdd" => 2, "\xde" => 2, "\xdf" => 2, + "\xe0" => 3, "\xe1" => 3, "\xe2" => 3, "\xe3" => 3, "\xe4" => 3, "\xe5" => 3, "\xe6" => 3, "\xe7" => 3, + "\xe8" => 3, "\xe9" => 3, "\xea" => 3, "\xeb" => 3, "\xec" => 3, "\xed" => 3, "\xee" => 3, "\xef" => 3, + "\xf0" => 4, "\xf1" => 4, "\xf2" => 4, "\xf3" => 4, "\xf4" => 4, "\xf5" => 4, "\xf6" => 4, "\xf7" => 4, + "\xf8" => 5, "\xf9" => 5, "\xfa" => 5, "\xfb" => 5, "\xfc" => 6, "\xfd" => 6, "\xfe" => 0, "\xff" => 0, + ]; + + private $data = ''; + private $dataSize = 0; + private $map = []; + private $charCount = 0; + private $currentPos = 0; + private $fixedWidth = 0; + + /** + * @param resource|string $input + */ + public function __construct($input, ?string $charset = 'utf-8') + { + $charset = strtolower(trim($charset)) ?: 'utf-8'; + if ('utf-8' === $charset || 'utf8' === $charset) { + $this->fixedWidth = 0; + $this->map = ['p' => [], 'i' => []]; + } else { + switch ($charset) { + // 16 bits + case 'ucs2': + case 'ucs-2': + case 'utf16': + case 'utf-16': + $this->fixedWidth = 2; + break; + + // 32 bits + case 'ucs4': + case 'ucs-4': + case 'utf32': + case 'utf-32': + $this->fixedWidth = 4; + break; + + // 7-8 bit charsets: (us-)?ascii, (iso|iec)-?8859-?[0-9]+, windows-?125[0-9], cp-?[0-9]+, ansi, macintosh, + // koi-?7, koi-?8-?.+, mik, (cork|t1), v?iscii + // and fallback + default: + $this->fixedWidth = 1; + } + } + if (\is_resource($input)) { + $blocks = 512; + if (stream_get_meta_data($input)['seekable'] ?? false) { + rewind($input); + } + while (false !== $read = fread($input, $blocks)) { + $this->write($read); + } + } else { + $this->write($input); + } + } + + public function read(int $length): ?string + { + if ($this->currentPos >= $this->charCount) { + return null; + } + $length = ($this->currentPos + $length > $this->charCount) ? $this->charCount - $this->currentPos : $length; + if ($this->fixedWidth > 0) { + $len = $length * $this->fixedWidth; + $ret = substr($this->data, $this->currentPos * $this->fixedWidth, $len); + $this->currentPos += $length; + } else { + $end = $this->currentPos + $length; + $end = $end > $this->charCount ? $this->charCount : $end; + $ret = ''; + $start = 0; + if ($this->currentPos > 0) { + $start = $this->map['p'][$this->currentPos - 1]; + } + $to = $start; + for (; $this->currentPos < $end; ++$this->currentPos) { + if (isset($this->map['i'][$this->currentPos])) { + $ret .= substr($this->data, $start, $to - $start).'?'; + $start = $this->map['p'][$this->currentPos]; + } else { + $to = $this->map['p'][$this->currentPos]; + } + } + $ret .= substr($this->data, $start, $to - $start); + } + + return $ret; + } + + public function readBytes(int $length): ?array + { + if (null !== $read = $this->read($length)) { + return array_map('ord', str_split($read, 1)); + } + + return null; + } + + public function setPointer(int $charOffset): void + { + if ($this->charCount < $charOffset) { + $charOffset = $this->charCount; + } + $this->currentPos = $charOffset; + } + + public function write(string $chars): void + { + $ignored = ''; + $this->data .= $chars; + if ($this->fixedWidth > 0) { + $strlen = \strlen($chars); + $ignoredL = $strlen % $this->fixedWidth; + $ignored = $ignoredL ? substr($chars, -$ignoredL) : ''; + $this->charCount += ($strlen - $ignoredL) / $this->fixedWidth; + } else { + $this->charCount += $this->getUtf8CharPositions($chars, $this->dataSize, $ignored); + } + $this->dataSize = \strlen($this->data) - \strlen($ignored); + } + + private function getUtf8CharPositions(string $string, int $startOffset, string &$ignoredChars): int + { + $strlen = \strlen($string); + $charPos = \count($this->map['p']); + $foundChars = 0; + $invalid = false; + for ($i = 0; $i < $strlen; ++$i) { + $char = $string[$i]; + $size = self::UTF8_LENGTH_MAP[$char]; + if (0 == $size) { + /* char is invalid, we must wait for a resync */ + $invalid = true; + continue; + } + + if ($invalid) { + /* We mark the chars as invalid and start a new char */ + $this->map['p'][$charPos + $foundChars] = $startOffset + $i; + $this->map['i'][$charPos + $foundChars] = true; + ++$foundChars; + $invalid = false; + } + if (($i + $size) > $strlen) { + $ignoredChars = substr($string, $i); + break; + } + for ($j = 1; $j < $size; ++$j) { + $char = $string[$i + $j]; + if ($char > "\x7F" && $char < "\xC0") { + // Valid - continue parsing + } else { + /* char is invalid, we must wait for a resync */ + $invalid = true; + continue 2; + } + } + /* Ok we got a complete char here */ + $this->map['p'][$charPos + $foundChars] = $startOffset + $i + $size; + $i += $j - 1; + ++$foundChars; + } + + return $foundChars; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Crypto/SMime.php b/lam/lib/3rdParty/composer/symfony/mime/Crypto/SMime.php new file mode 100644 index 00000000..55941be9 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Crypto/SMime.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Crypto; + +use Symfony\Component\Mime\Exception\RuntimeException; +use Symfony\Component\Mime\Part\SMimePart; + +/** + * @author Sebastiaan Stok + * + * @internal + */ +abstract class SMime +{ + protected function normalizeFilePath(string $path): string + { + if (!file_exists($path)) { + throw new RuntimeException(sprintf('File does not exist: %s.', $path)); + } + + return 'file://'.str_replace('\\', '/', realpath($path)); + } + + protected function iteratorToFile(iterable $iterator, $stream): void + { + foreach ($iterator as $chunk) { + fwrite($stream, $chunk); + } + } + + protected function convertMessageToSMimePart($stream, string $type, string $subtype): SMimePart + { + rewind($stream); + + $headers = ''; + + while (!feof($stream)) { + $buffer = fread($stream, 78); + $headers .= $buffer; + + // Detect ending of header list + if (preg_match('/(\r\n\r\n|\n\n)/', $headers, $match)) { + $headersPosEnd = strpos($headers, $headerBodySeparator = $match[0]); + + break; + } + } + + $headers = $this->getMessageHeaders(trim(substr($headers, 0, $headersPosEnd))); + + fseek($stream, $headersPosEnd + \strlen($headerBodySeparator)); + + return new SMimePart($this->getStreamIterator($stream), $type, $subtype, $this->getParametersFromHeader($headers['content-type'])); + } + + protected function getStreamIterator($stream): iterable + { + while (!feof($stream)) { + yield fread($stream, 16372); + } + } + + private function getMessageHeaders(string $headerData): array + { + $headers = []; + $headerLines = explode("\r\n", str_replace("\n", "\r\n", str_replace("\r\n", "\n", $headerData))); + $currentHeaderName = ''; + + // Transform header lines into an associative array + foreach ($headerLines as $headerLine) { + // Empty lines between headers indicate a new mime-entity + if ('' === $headerLine) { + break; + } + + // Handle headers that span multiple lines + if (false === strpos($headerLine, ':')) { + $headers[$currentHeaderName] .= ' '.trim($headerLine); + continue; + } + + $header = explode(':', $headerLine, 2); + $currentHeaderName = strtolower($header[0]); + $headers[$currentHeaderName] = trim($header[1]); + } + + return $headers; + } + + private function getParametersFromHeader(string $header): array + { + $params = []; + + preg_match_all('/(?P[a-z-0-9]+)=(?P"[^"]+"|(?:[^\s;]+|$))(?:\s+;)?/i', $header, $matches); + + foreach ($matches['value'] as $pos => $paramValue) { + $params[$matches['name'][$pos]] = trim($paramValue, '"'); + } + + return $params; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Crypto/SMimeEncrypter.php b/lam/lib/3rdParty/composer/symfony/mime/Crypto/SMimeEncrypter.php new file mode 100644 index 00000000..d6961a6e --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Crypto/SMimeEncrypter.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Crypto; + +use Symfony\Component\Mime\Exception\RuntimeException; +use Symfony\Component\Mime\Message; + +/** + * @author Sebastiaan Stok + */ +final class SMimeEncrypter extends SMime +{ + private $certs; + private $cipher; + + /** + * @param string|string[] $certificate The path (or array of paths) of the file(s) containing the X.509 certificate(s) + * @param int|null $cipher A set of algorithms used to encrypt the message. Must be one of these PHP constants: https://www.php.net/manual/en/openssl.ciphers.php + */ + public function __construct($certificate, int $cipher = null) + { + if (!\extension_loaded('openssl')) { + throw new \LogicException('PHP extension "openssl" is required to use SMime.'); + } + + if (\is_array($certificate)) { + $this->certs = array_map([$this, 'normalizeFilePath'], $certificate); + } else { + $this->certs = $this->normalizeFilePath($certificate); + } + + $this->cipher = $cipher ?? OPENSSL_CIPHER_AES_256_CBC; + } + + public function encrypt(Message $message): Message + { + $bufferFile = tmpfile(); + $outputFile = tmpfile(); + + $this->iteratorToFile($message->toIterable(), $bufferFile); + + if (!@openssl_pkcs7_encrypt(stream_get_meta_data($bufferFile)['uri'], stream_get_meta_data($outputFile)['uri'], $this->certs, [], 0, $this->cipher)) { + throw new RuntimeException(sprintf('Failed to encrypt S/Mime message. Error: "%s".', openssl_error_string())); + } + + $mimePart = $this->convertMessageToSMimePart($outputFile, 'application', 'pkcs7-mime'); + $mimePart->getHeaders() + ->addTextHeader('Content-Transfer-Encoding', 'base64') + ->addParameterizedHeader('Content-Disposition', 'attachment', ['name' => 'smime.p7m']) + ; + + return new Message($message->getHeaders(), $mimePart); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Crypto/SMimeSigner.php b/lam/lib/3rdParty/composer/symfony/mime/Crypto/SMimeSigner.php new file mode 100644 index 00000000..243aaf10 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Crypto/SMimeSigner.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Crypto; + +use Symfony\Component\Mime\Exception\RuntimeException; +use Symfony\Component\Mime\Message; + +/** + * @author Sebastiaan Stok + */ +final class SMimeSigner extends SMime +{ + private $signCertificate; + private $signPrivateKey; + private $signOptions; + private $extraCerts; + + /** + * @var string|null + */ + private $privateKeyPassphrase; + + /** + * @param string $certificate The path of the file containing the signing certificate (in PEM format) + * @param string $privateKey The path of the file containing the private key (in PEM format) + * @param string|null $privateKeyPassphrase A passphrase of the private key (if any) + * @param string|null $extraCerts The path of the file containing intermediate certificates (in PEM format) needed by the signing certificate + * @param int|null $signOptions Bitwise operator options for openssl_pkcs7_sign() (@see https://secure.php.net/manual/en/openssl.pkcs7.flags.php) + */ + public function __construct(string $certificate, string $privateKey, string $privateKeyPassphrase = null, string $extraCerts = null, int $signOptions = null) + { + if (!\extension_loaded('openssl')) { + throw new \LogicException('PHP extension "openssl" is required to use SMime.'); + } + + $this->signCertificate = $this->normalizeFilePath($certificate); + + if (null !== $privateKeyPassphrase) { + $this->signPrivateKey = [$this->normalizeFilePath($privateKey), $privateKeyPassphrase]; + } else { + $this->signPrivateKey = $this->normalizeFilePath($privateKey); + } + + $this->signOptions = $signOptions ?? PKCS7_DETACHED; + $this->extraCerts = $extraCerts ? realpath($extraCerts) : null; + $this->privateKeyPassphrase = $privateKeyPassphrase; + } + + public function sign(Message $message): Message + { + $bufferFile = tmpfile(); + $outputFile = tmpfile(); + + $this->iteratorToFile($message->getBody()->toIterable(), $bufferFile); + + if (!@openssl_pkcs7_sign(stream_get_meta_data($bufferFile)['uri'], stream_get_meta_data($outputFile)['uri'], $this->signCertificate, $this->signPrivateKey, [], $this->signOptions, $this->extraCerts)) { + throw new RuntimeException(sprintf('Failed to sign S/Mime message. Error: "%s".', openssl_error_string())); + } + + return new Message($message->getHeaders(), $this->convertMessageToSMimePart($outputFile, 'multipart', 'signed')); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php b/lam/lib/3rdParty/composer/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php new file mode 100644 index 00000000..e24beb0d --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Registers custom mime types guessers. + * + * @author Fabien Potencier + */ +class AddMimeTypeGuesserPass implements CompilerPassInterface +{ + private $mimeTypesService; + private $mimeTypeGuesserTag; + + public function __construct(string $mimeTypesService = 'mime_types', string $mimeTypeGuesserTag = 'mime.mime_type_guesser') + { + $this->mimeTypesService = $mimeTypesService; + $this->mimeTypeGuesserTag = $mimeTypeGuesserTag; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if ($container->has($this->mimeTypesService)) { + $definition = $container->findDefinition($this->mimeTypesService); + foreach ($container->findTaggedServiceIds($this->mimeTypeGuesserTag, true) as $id => $attributes) { + $definition->addMethodCall('registerGuesser', [new Reference($id)]); + } + } + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Email.php b/lam/lib/3rdParty/composer/symfony/mime/Email.php new file mode 100644 index 00000000..fb912621 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Email.php @@ -0,0 +1,599 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Exception\LogicException; +use Symfony\Component\Mime\Part\AbstractPart; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\Multipart\AlternativePart; +use Symfony\Component\Mime\Part\Multipart\MixedPart; +use Symfony\Component\Mime\Part\Multipart\RelatedPart; +use Symfony\Component\Mime\Part\TextPart; + +/** + * @author Fabien Potencier + */ +class Email extends Message +{ + const PRIORITY_HIGHEST = 1; + const PRIORITY_HIGH = 2; + const PRIORITY_NORMAL = 3; + const PRIORITY_LOW = 4; + const PRIORITY_LOWEST = 5; + + private const PRIORITY_MAP = [ + self::PRIORITY_HIGHEST => 'Highest', + self::PRIORITY_HIGH => 'High', + self::PRIORITY_NORMAL => 'Normal', + self::PRIORITY_LOW => 'Low', + self::PRIORITY_LOWEST => 'Lowest', + ]; + + private $text; + private $textCharset; + private $html; + private $htmlCharset; + private $attachments = []; + + /** + * @return $this + */ + public function subject(string $subject) + { + return $this->setHeaderBody('Text', 'Subject', $subject); + } + + public function getSubject(): ?string + { + return $this->getHeaders()->getHeaderBody('Subject'); + } + + /** + * @return $this + */ + public function date(\DateTimeInterface $dateTime) + { + return $this->setHeaderBody('Date', 'Date', $dateTime); + } + + public function getDate(): ?\DateTimeImmutable + { + return $this->getHeaders()->getHeaderBody('Date'); + } + + /** + * @param Address|string $address + * + * @return $this + */ + public function returnPath($address) + { + return $this->setHeaderBody('Path', 'Return-Path', Address::create($address)); + } + + public function getReturnPath(): ?Address + { + return $this->getHeaders()->getHeaderBody('Return-Path'); + } + + /** + * @param Address|string $address + * + * @return $this + */ + public function sender($address) + { + return $this->setHeaderBody('Mailbox', 'Sender', Address::create($address)); + } + + public function getSender(): ?Address + { + return $this->getHeaders()->getHeaderBody('Sender'); + } + + /** + * @param Address|string ...$addresses + * + * @return $this + */ + public function addFrom(...$addresses) + { + return $this->addListAddressHeaderBody('From', $addresses); + } + + /** + * @param Address|string ...$addresses + * + * @return $this + */ + public function from(...$addresses) + { + return $this->setListAddressHeaderBody('From', $addresses); + } + + /** + * @return Address[] + */ + public function getFrom(): array + { + return $this->getHeaders()->getHeaderBody('From') ?: []; + } + + /** + * @param Address|string ...$addresses + * + * @return $this + */ + public function addReplyTo(...$addresses) + { + return $this->addListAddressHeaderBody('Reply-To', $addresses); + } + + /** + * @param Address|string ...$addresses + * + * @return $this + */ + public function replyTo(...$addresses) + { + return $this->setListAddressHeaderBody('Reply-To', $addresses); + } + + /** + * @return Address[] + */ + public function getReplyTo(): array + { + return $this->getHeaders()->getHeaderBody('Reply-To') ?: []; + } + + /** + * @param Address|string ...$addresses + * + * @return $this + */ + public function addTo(...$addresses) + { + return $this->addListAddressHeaderBody('To', $addresses); + } + + /** + * @param Address|string ...$addresses + * + * @return $this + */ + public function to(...$addresses) + { + return $this->setListAddressHeaderBody('To', $addresses); + } + + /** + * @return Address[] + */ + public function getTo(): array + { + return $this->getHeaders()->getHeaderBody('To') ?: []; + } + + /** + * @param Address|string ...$addresses + * + * @return $this + */ + public function addCc(...$addresses) + { + return $this->addListAddressHeaderBody('Cc', $addresses); + } + + /** + * @param Address|string ...$addresses + * + * @return $this + */ + public function cc(...$addresses) + { + return $this->setListAddressHeaderBody('Cc', $addresses); + } + + /** + * @return Address[] + */ + public function getCc(): array + { + return $this->getHeaders()->getHeaderBody('Cc') ?: []; + } + + /** + * @param Address|string ...$addresses + * + * @return $this + */ + public function addBcc(...$addresses) + { + return $this->addListAddressHeaderBody('Bcc', $addresses); + } + + /** + * @param Address|string ...$addresses + * + * @return $this + */ + public function bcc(...$addresses) + { + return $this->setListAddressHeaderBody('Bcc', $addresses); + } + + /** + * @return Address[] + */ + public function getBcc(): array + { + return $this->getHeaders()->getHeaderBody('Bcc') ?: []; + } + + /** + * Sets the priority of this message. + * + * The value is an integer where 1 is the highest priority and 5 is the lowest. + * + * @return $this + */ + public function priority(int $priority) + { + if ($priority > 5) { + $priority = 5; + } elseif ($priority < 1) { + $priority = 1; + } + + return $this->setHeaderBody('Text', 'X-Priority', sprintf('%d (%s)', $priority, self::PRIORITY_MAP[$priority])); + } + + /** + * Get the priority of this message. + * + * The returned value is an integer where 1 is the highest priority and 5 + * is the lowest. + */ + public function getPriority(): int + { + list($priority) = sscanf($this->getHeaders()->getHeaderBody('X-Priority'), '%[1-5]'); + + return $priority ?? 3; + } + + /** + * @param resource|string $body + * + * @return $this + */ + public function text($body, string $charset = 'utf-8') + { + $this->text = $body; + $this->textCharset = $charset; + + return $this; + } + + /** + * @return resource|string|null + */ + public function getTextBody() + { + return $this->text; + } + + public function getTextCharset(): ?string + { + return $this->textCharset; + } + + /** + * @param resource|string|null $body + * + * @return $this + */ + public function html($body, string $charset = 'utf-8') + { + $this->html = $body; + $this->htmlCharset = $charset; + + return $this; + } + + /** + * @return resource|string|null + */ + public function getHtmlBody() + { + return $this->html; + } + + public function getHtmlCharset(): ?string + { + return $this->htmlCharset; + } + + /** + * @param resource|string $body + * + * @return $this + */ + public function attach($body, string $name = null, string $contentType = null) + { + $this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => false]; + + return $this; + } + + /** + * @return $this + */ + public function attachFromPath(string $path, string $name = null, string $contentType = null) + { + $this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => false]; + + return $this; + } + + /** + * @param resource|string $body + * + * @return $this + */ + public function embed($body, string $name = null, string $contentType = null) + { + $this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => true]; + + return $this; + } + + /** + * @return $this + */ + public function embedFromPath(string $path, string $name = null, string $contentType = null) + { + $this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => true]; + + return $this; + } + + /** + * @return $this + */ + public function attachPart(DataPart $part) + { + $this->attachments[] = ['part' => $part]; + + return $this; + } + + /** + * @return DataPart[] + */ + public function getAttachments(): array + { + $parts = []; + foreach ($this->attachments as $attachment) { + $parts[] = $this->createDataPart($attachment); + } + + return $parts; + } + + public function getBody(): AbstractPart + { + if (null !== $body = parent::getBody()) { + return $body; + } + + return $this->generateBody(); + } + + public function ensureValidity() + { + if (null === $this->text && null === $this->html && !$this->attachments) { + throw new LogicException('A message must have a text or an HTML part or attachments.'); + } + + parent::ensureValidity(); + } + + /** + * Generates an AbstractPart based on the raw body of a message. + * + * The most "complex" part generated by this method is when there is text and HTML bodies + * with related images for the HTML part and some attachments: + * + * multipart/mixed + * | + * |------------> multipart/related + * | | + * | |------------> multipart/alternative + * | | | + * | | ------------> text/plain (with content) + * | | | + * | | ------------> text/html (with content) + * | | + * | ------------> image/png (with content) + * | + * ------------> application/pdf (with content) + */ + private function generateBody(): AbstractPart + { + $this->ensureValidity(); + + [$htmlPart, $attachmentParts, $inlineParts] = $this->prepareParts(); + + $part = null === $this->text ? null : new TextPart($this->text, $this->textCharset); + if (null !== $htmlPart) { + if (null !== $part) { + $part = new AlternativePart($part, $htmlPart); + } else { + $part = $htmlPart; + } + } + + if ($inlineParts) { + $part = new RelatedPart($part, ...$inlineParts); + } + + if ($attachmentParts) { + if ($part) { + $part = new MixedPart($part, ...$attachmentParts); + } else { + $part = new MixedPart(...$attachmentParts); + } + } + + return $part; + } + + private function prepareParts(): ?array + { + $names = []; + $htmlPart = null; + $html = $this->html; + if (null !== $this->html) { + if (\is_resource($html)) { + if (stream_get_meta_data($html)['seekable'] ?? false) { + rewind($html); + } + + $html = stream_get_contents($html); + } + $htmlPart = new TextPart($html, $this->htmlCharset, 'html'); + preg_match_all('(]*src\s*=\s*(?:([\'"])cid:([^"]+)\\1|cid:([^>\s]+)))i', $html, $names); + $names = array_filter(array_unique(array_merge($names[2], $names[3]))); + } + + $attachmentParts = $inlineParts = []; + foreach ($this->attachments as $attachment) { + foreach ($names as $name) { + if (isset($attachment['part'])) { + continue; + } + if ($name !== $attachment['name']) { + continue; + } + if (isset($inlineParts[$name])) { + continue 2; + } + $attachment['inline'] = true; + $inlineParts[$name] = $part = $this->createDataPart($attachment); + $html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html); + continue 2; + } + $attachmentParts[] = $this->createDataPart($attachment); + } + if (null !== $htmlPart) { + $htmlPart = new TextPart($html, $this->htmlCharset, 'html'); + } + + return [$htmlPart, $attachmentParts, array_values($inlineParts)]; + } + + private function createDataPart(array $attachment): DataPart + { + if (isset($attachment['part'])) { + return $attachment['part']; + } + + if (isset($attachment['body'])) { + $part = new DataPart($attachment['body'], $attachment['name'] ?? null, $attachment['content-type'] ?? null); + } else { + $part = DataPart::fromPath($attachment['path'] ?? '', $attachment['name'] ?? null, $attachment['content-type'] ?? null); + } + if ($attachment['inline']) { + $part->asInline(); + } + + return $part; + } + + /** + * @return $this + */ + private function setHeaderBody(string $type, string $name, $body): object + { + $this->getHeaders()->setHeaderBody($type, $name, $body); + + return $this; + } + + private function addListAddressHeaderBody(string $name, array $addresses) + { + if (!$header = $this->getHeaders()->get($name)) { + return $this->setListAddressHeaderBody($name, $addresses); + } + $header->addAddresses(Address::createArray($addresses)); + + return $this; + } + + private function setListAddressHeaderBody(string $name, array $addresses) + { + $addresses = Address::createArray($addresses); + $headers = $this->getHeaders(); + if ($header = $headers->get($name)) { + $header->setAddresses($addresses); + } else { + $headers->addMailboxListHeader($name, $addresses); + } + + return $this; + } + + /** + * @internal + */ + public function __serialize(): array + { + if (\is_resource($this->text)) { + if (stream_get_meta_data($this->text)['seekable'] ?? false) { + rewind($this->text); + } + + $this->text = stream_get_contents($this->text); + } + + if (\is_resource($this->html)) { + if (stream_get_meta_data($this->html)['seekable'] ?? false) { + rewind($this->html); + } + + $this->html = stream_get_contents($this->html); + } + + foreach ($this->attachments as $i => $attachment) { + if (isset($attachment['body']) && \is_resource($attachment['body'])) { + if (stream_get_meta_data($attachment['body'])['seekable'] ?? false) { + rewind($attachment['body']); + } + + $this->attachments[$i]['body'] = stream_get_contents($attachment['body']); + } + } + + return [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, parent::__serialize()]; + } + + /** + * @internal + */ + public function __unserialize(array $data): void + { + [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, $parentData] = $data; + + parent::__unserialize($parentData); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Encoder/AddressEncoderInterface.php b/lam/lib/3rdParty/composer/symfony/mime/Encoder/AddressEncoderInterface.php new file mode 100644 index 00000000..de477d88 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Encoder/AddressEncoderInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +use Symfony\Component\Mime\Exception\AddressEncoderException; + +/** + * @author Christian Schmidt + */ +interface AddressEncoderInterface +{ + /** + * Encodes an email address. + * + * @throws AddressEncoderException if the email cannot be represented in + * the encoding implemented by this class + */ + public function encodeString(string $address): string; +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Encoder/Base64ContentEncoder.php b/lam/lib/3rdParty/composer/symfony/mime/Encoder/Base64ContentEncoder.php new file mode 100644 index 00000000..338490b3 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Encoder/Base64ContentEncoder.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +use Symfony\Component\Mime\Exception\RuntimeException; + +/** + * @author Fabien Potencier + */ +final class Base64ContentEncoder extends Base64Encoder implements ContentEncoderInterface +{ + public function encodeByteStream($stream, int $maxLineLength = 0): iterable + { + if (!\is_resource($stream)) { + throw new \TypeError(sprintf('Method "%s" takes a stream as a first argument.', __METHOD__)); + } + + $filter = stream_filter_append($stream, 'convert.base64-encode', \STREAM_FILTER_READ, [ + 'line-length' => 0 >= $maxLineLength || 76 < $maxLineLength ? 76 : $maxLineLength, + 'line-break-chars' => "\r\n", + ]); + if (!\is_resource($filter)) { + throw new RuntimeException('Unable to set the base64 content encoder to the filter.'); + } + + if (stream_get_meta_data($stream)['seekable'] ?? false) { + rewind($stream); + } + while (!feof($stream)) { + yield fread($stream, 8192); + } + stream_filter_remove($filter); + } + + public function getName(): string + { + return 'base64'; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Encoder/Base64Encoder.php b/lam/lib/3rdParty/composer/symfony/mime/Encoder/Base64Encoder.php new file mode 100644 index 00000000..71064785 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Encoder/Base64Encoder.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Chris Corbyn + */ +class Base64Encoder implements EncoderInterface +{ + /** + * Takes an unencoded string and produces a Base64 encoded string from it. + * + * Base64 encoded strings have a maximum line length of 76 characters. + * If the first line needs to be shorter, indicate the difference with + * $firstLineOffset. + */ + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string + { + if (0 >= $maxLineLength || 76 < $maxLineLength) { + $maxLineLength = 76; + } + + $encodedString = base64_encode($string); + $firstLine = ''; + if (0 !== $firstLineOffset) { + $firstLine = substr($encodedString, 0, $maxLineLength - $firstLineOffset)."\r\n"; + $encodedString = substr($encodedString, $maxLineLength - $firstLineOffset); + } + + return $firstLine.trim(chunk_split($encodedString, $maxLineLength, "\r\n")); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Encoder/Base64MimeHeaderEncoder.php b/lam/lib/3rdParty/composer/symfony/mime/Encoder/Base64MimeHeaderEncoder.php new file mode 100644 index 00000000..5c06f6d9 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Encoder/Base64MimeHeaderEncoder.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Chris Corbyn + */ +final class Base64MimeHeaderEncoder extends Base64Encoder implements MimeHeaderEncoderInterface +{ + public function getName(): string + { + return 'B'; + } + + /** + * Takes an unencoded string and produces a Base64 encoded string from it. + * + * If the charset is iso-2022-jp, it uses mb_encode_mimeheader instead of + * default encodeString, otherwise pass to the parent method. + */ + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string + { + if ('iso-2022-jp' === strtolower($charset)) { + $old = mb_internal_encoding(); + mb_internal_encoding('utf-8'); + $newstring = mb_encode_mimeheader($string, 'iso-2022-jp', $this->getName(), "\r\n"); + mb_internal_encoding($old); + + return $newstring; + } + + return parent::encodeString($string, $charset, $firstLineOffset, $maxLineLength); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Encoder/ContentEncoderInterface.php b/lam/lib/3rdParty/composer/symfony/mime/Encoder/ContentEncoderInterface.php new file mode 100644 index 00000000..a45ad04c --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Encoder/ContentEncoderInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Chris Corbyn + */ +interface ContentEncoderInterface extends EncoderInterface +{ + /** + * Encodes the stream to a Generator. + * + * @param resource $stream + */ + public function encodeByteStream($stream, int $maxLineLength = 0): iterable; + + /** + * Gets the MIME name of this content encoding scheme. + */ + public function getName(): string; +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Encoder/EightBitContentEncoder.php b/lam/lib/3rdParty/composer/symfony/mime/Encoder/EightBitContentEncoder.php new file mode 100644 index 00000000..82831209 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Encoder/EightBitContentEncoder.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Fabien Potencier + */ +final class EightBitContentEncoder implements ContentEncoderInterface +{ + public function encodeByteStream($stream, int $maxLineLength = 0): iterable + { + while (!feof($stream)) { + yield fread($stream, 16372); + } + } + + public function getName(): string + { + return '8bit'; + } + + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string + { + return $string; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Encoder/EncoderInterface.php b/lam/lib/3rdParty/composer/symfony/mime/Encoder/EncoderInterface.php new file mode 100644 index 00000000..bbf6d488 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Encoder/EncoderInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Chris Corbyn + */ +interface EncoderInterface +{ + /** + * Encode a given string to produce an encoded string. + * + * @param int $firstLineOffset if first line needs to be shorter + * @param int $maxLineLength - 0 indicates the default length for this encoding + */ + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string; +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Encoder/IdnAddressEncoder.php b/lam/lib/3rdParty/composer/symfony/mime/Encoder/IdnAddressEncoder.php new file mode 100644 index 00000000..1c5e32c0 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Encoder/IdnAddressEncoder.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +use Symfony\Component\Mime\Exception\AddressEncoderException; + +/** + * An IDN email address encoder. + * + * Encodes the domain part of an address using IDN. This is compatible will all + * SMTP servers. + * + * This encoder does not support email addresses with non-ASCII characters in + * local-part (the substring before @). To send to such addresses, use + * Utf8AddressEncoder together with SmtpUtf8Handler. Your outbound SMTP server must support + * the SMTPUTF8 extension. + * + * @author Christian Schmidt + */ +final class IdnAddressEncoder implements AddressEncoderInterface +{ + /** + * Encodes the domain part of an address using IDN. + * + * @throws AddressEncoderException If local-part contains non-ASCII characters + */ + public function encodeString(string $address): string + { + $i = strrpos($address, '@'); + if (false !== $i) { + $local = substr($address, 0, $i); + $domain = substr($address, $i + 1); + + if (preg_match('/[^\x00-\x7F]/', $local)) { + throw new AddressEncoderException(sprintf('Non-ASCII characters not supported in local-part os "%s".', $address)); + } + + if (preg_match('/[^\x00-\x7F]/', $domain)) { + $address = sprintf('%s@%s', $local, idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46)); + } + } + + return $address; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Encoder/MimeHeaderEncoderInterface.php b/lam/lib/3rdParty/composer/symfony/mime/Encoder/MimeHeaderEncoderInterface.php new file mode 100644 index 00000000..fff2c782 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Encoder/MimeHeaderEncoderInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Chris Corbyn + */ +interface MimeHeaderEncoderInterface +{ + /** + * Get the MIME name of this content encoding scheme. + */ + public function getName(): string; +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Encoder/QpContentEncoder.php b/lam/lib/3rdParty/composer/symfony/mime/Encoder/QpContentEncoder.php new file mode 100644 index 00000000..e0b8605d --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Encoder/QpContentEncoder.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Lars Strojny + */ +final class QpContentEncoder implements ContentEncoderInterface +{ + public function encodeByteStream($stream, int $maxLineLength = 0): iterable + { + if (!\is_resource($stream)) { + throw new \TypeError(sprintf('Method "%s" takes a stream as a first argument.', __METHOD__)); + } + + // we don't use PHP stream filters here as the content should be small enough + if (stream_get_meta_data($stream)['seekable'] ?? false) { + rewind($stream); + } + + yield $this->encodeString(stream_get_contents($stream), 'utf-8', 0, $maxLineLength); + } + + public function getName(): string + { + return 'quoted-printable'; + } + + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string + { + return $this->standardize(quoted_printable_encode($string)); + } + + /** + * Make sure CRLF is correct and HT/SPACE are in valid places. + */ + private function standardize(string $string): string + { + // transform CR or LF to CRLF + $string = preg_replace('~=0D(?!=0A)|(? + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +use Symfony\Component\Mime\CharacterStream; + +/** + * @author Chris Corbyn + */ +class QpEncoder implements EncoderInterface +{ + /** + * Pre-computed QP for HUGE optimization. + */ + private static $qpMap = [ + 0 => '=00', 1 => '=01', 2 => '=02', 3 => '=03', 4 => '=04', + 5 => '=05', 6 => '=06', 7 => '=07', 8 => '=08', 9 => '=09', + 10 => '=0A', 11 => '=0B', 12 => '=0C', 13 => '=0D', 14 => '=0E', + 15 => '=0F', 16 => '=10', 17 => '=11', 18 => '=12', 19 => '=13', + 20 => '=14', 21 => '=15', 22 => '=16', 23 => '=17', 24 => '=18', + 25 => '=19', 26 => '=1A', 27 => '=1B', 28 => '=1C', 29 => '=1D', + 30 => '=1E', 31 => '=1F', 32 => '=20', 33 => '=21', 34 => '=22', + 35 => '=23', 36 => '=24', 37 => '=25', 38 => '=26', 39 => '=27', + 40 => '=28', 41 => '=29', 42 => '=2A', 43 => '=2B', 44 => '=2C', + 45 => '=2D', 46 => '=2E', 47 => '=2F', 48 => '=30', 49 => '=31', + 50 => '=32', 51 => '=33', 52 => '=34', 53 => '=35', 54 => '=36', + 55 => '=37', 56 => '=38', 57 => '=39', 58 => '=3A', 59 => '=3B', + 60 => '=3C', 61 => '=3D', 62 => '=3E', 63 => '=3F', 64 => '=40', + 65 => '=41', 66 => '=42', 67 => '=43', 68 => '=44', 69 => '=45', + 70 => '=46', 71 => '=47', 72 => '=48', 73 => '=49', 74 => '=4A', + 75 => '=4B', 76 => '=4C', 77 => '=4D', 78 => '=4E', 79 => '=4F', + 80 => '=50', 81 => '=51', 82 => '=52', 83 => '=53', 84 => '=54', + 85 => '=55', 86 => '=56', 87 => '=57', 88 => '=58', 89 => '=59', + 90 => '=5A', 91 => '=5B', 92 => '=5C', 93 => '=5D', 94 => '=5E', + 95 => '=5F', 96 => '=60', 97 => '=61', 98 => '=62', 99 => '=63', + 100 => '=64', 101 => '=65', 102 => '=66', 103 => '=67', 104 => '=68', + 105 => '=69', 106 => '=6A', 107 => '=6B', 108 => '=6C', 109 => '=6D', + 110 => '=6E', 111 => '=6F', 112 => '=70', 113 => '=71', 114 => '=72', + 115 => '=73', 116 => '=74', 117 => '=75', 118 => '=76', 119 => '=77', + 120 => '=78', 121 => '=79', 122 => '=7A', 123 => '=7B', 124 => '=7C', + 125 => '=7D', 126 => '=7E', 127 => '=7F', 128 => '=80', 129 => '=81', + 130 => '=82', 131 => '=83', 132 => '=84', 133 => '=85', 134 => '=86', + 135 => '=87', 136 => '=88', 137 => '=89', 138 => '=8A', 139 => '=8B', + 140 => '=8C', 141 => '=8D', 142 => '=8E', 143 => '=8F', 144 => '=90', + 145 => '=91', 146 => '=92', 147 => '=93', 148 => '=94', 149 => '=95', + 150 => '=96', 151 => '=97', 152 => '=98', 153 => '=99', 154 => '=9A', + 155 => '=9B', 156 => '=9C', 157 => '=9D', 158 => '=9E', 159 => '=9F', + 160 => '=A0', 161 => '=A1', 162 => '=A2', 163 => '=A3', 164 => '=A4', + 165 => '=A5', 166 => '=A6', 167 => '=A7', 168 => '=A8', 169 => '=A9', + 170 => '=AA', 171 => '=AB', 172 => '=AC', 173 => '=AD', 174 => '=AE', + 175 => '=AF', 176 => '=B0', 177 => '=B1', 178 => '=B2', 179 => '=B3', + 180 => '=B4', 181 => '=B5', 182 => '=B6', 183 => '=B7', 184 => '=B8', + 185 => '=B9', 186 => '=BA', 187 => '=BB', 188 => '=BC', 189 => '=BD', + 190 => '=BE', 191 => '=BF', 192 => '=C0', 193 => '=C1', 194 => '=C2', + 195 => '=C3', 196 => '=C4', 197 => '=C5', 198 => '=C6', 199 => '=C7', + 200 => '=C8', 201 => '=C9', 202 => '=CA', 203 => '=CB', 204 => '=CC', + 205 => '=CD', 206 => '=CE', 207 => '=CF', 208 => '=D0', 209 => '=D1', + 210 => '=D2', 211 => '=D3', 212 => '=D4', 213 => '=D5', 214 => '=D6', + 215 => '=D7', 216 => '=D8', 217 => '=D9', 218 => '=DA', 219 => '=DB', + 220 => '=DC', 221 => '=DD', 222 => '=DE', 223 => '=DF', 224 => '=E0', + 225 => '=E1', 226 => '=E2', 227 => '=E3', 228 => '=E4', 229 => '=E5', + 230 => '=E6', 231 => '=E7', 232 => '=E8', 233 => '=E9', 234 => '=EA', + 235 => '=EB', 236 => '=EC', 237 => '=ED', 238 => '=EE', 239 => '=EF', + 240 => '=F0', 241 => '=F1', 242 => '=F2', 243 => '=F3', 244 => '=F4', + 245 => '=F5', 246 => '=F6', 247 => '=F7', 248 => '=F8', 249 => '=F9', + 250 => '=FA', 251 => '=FB', 252 => '=FC', 253 => '=FD', 254 => '=FE', + 255 => '=FF', + ]; + + private static $safeMapShare = []; + + /** + * A map of non-encoded ascii characters. + * + * @var string[] + * + * @internal + */ + protected $safeMap = []; + + public function __construct() + { + $id = \get_class($this); + if (!isset(self::$safeMapShare[$id])) { + $this->initSafeMap(); + self::$safeMapShare[$id] = $this->safeMap; + } else { + $this->safeMap = self::$safeMapShare[$id]; + } + } + + protected function initSafeMap(): void + { + foreach (array_merge([0x09, 0x20], range(0x21, 0x3C), range(0x3E, 0x7E)) as $byte) { + $this->safeMap[$byte] = \chr($byte); + } + } + + /** + * {@inheritdoc} + * + * Takes an unencoded string and produces a QP encoded string from it. + * + * QP encoded strings have a maximum line length of 76 characters. + * If the first line needs to be shorter, indicate the difference with + * $firstLineOffset. + */ + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string + { + if ($maxLineLength > 76 || $maxLineLength <= 0) { + $maxLineLength = 76; + } + + $thisLineLength = $maxLineLength - $firstLineOffset; + + $lines = []; + $lNo = 0; + $lines[$lNo] = ''; + $currentLine = &$lines[$lNo++]; + $size = $lineLen = 0; + $charStream = new CharacterStream($string, $charset); + + // Fetching more than 4 chars at one is slower, as is fetching fewer bytes + // Conveniently 4 chars is the UTF-8 safe number since UTF-8 has up to 6 + // bytes per char and (6 * 4 * 3 = 72 chars per line) * =NN is 3 bytes + while (null !== $bytes = $charStream->readBytes(4)) { + $enc = $this->encodeByteSequence($bytes, $size); + + $i = strpos($enc, '=0D=0A'); + $newLineLength = $lineLen + (false === $i ? $size : $i); + + if ($currentLine && $newLineLength >= $thisLineLength) { + $lines[$lNo] = ''; + $currentLine = &$lines[$lNo++]; + $thisLineLength = $maxLineLength; + $lineLen = 0; + } + + $currentLine .= $enc; + + if (false === $i) { + $lineLen += $size; + } else { + // 6 is the length of '=0D=0A'. + $lineLen = $size - strrpos($enc, '=0D=0A') - 6; + } + } + + return $this->standardize(implode("=\r\n", $lines)); + } + + /** + * Encode the given byte array into a verbatim QP form. + */ + private function encodeByteSequence(array $bytes, int &$size): string + { + $ret = ''; + $size = 0; + foreach ($bytes as $b) { + if (isset($this->safeMap[$b])) { + $ret .= $this->safeMap[$b]; + ++$size; + } else { + $ret .= self::$qpMap[$b]; + $size += 3; + } + } + + return $ret; + } + + /** + * Make sure CRLF is correct and HT/SPACE are in valid places. + */ + private function standardize(string $string): string + { + $string = str_replace(["\t=0D=0A", ' =0D=0A', '=0D=0A'], ["=09\r\n", "=20\r\n", "\r\n"], $string); + switch ($end = \ord(substr($string, -1))) { + case 0x09: + case 0x20: + $string = substr_replace($string, self::$qpMap[$end], -1); + } + + return $string; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Encoder/QpMimeHeaderEncoder.php b/lam/lib/3rdParty/composer/symfony/mime/Encoder/QpMimeHeaderEncoder.php new file mode 100644 index 00000000..d1d38375 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Encoder/QpMimeHeaderEncoder.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +/** + * @author Chris Corbyn + */ +final class QpMimeHeaderEncoder extends QpEncoder implements MimeHeaderEncoderInterface +{ + protected function initSafeMap(): void + { + foreach (array_merge( + range(0x61, 0x7A), range(0x41, 0x5A), + range(0x30, 0x39), [0x20, 0x21, 0x2A, 0x2B, 0x2D, 0x2F] + ) as $byte) { + $this->safeMap[$byte] = \chr($byte); + } + } + + public function getName(): string + { + return 'Q'; + } + + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string + { + return str_replace([' ', '=20', "=\r\n"], ['_', '_', "\r\n"], + parent::encodeString($string, $charset, $firstLineOffset, $maxLineLength) + ); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Encoder/Rfc2231Encoder.php b/lam/lib/3rdParty/composer/symfony/mime/Encoder/Rfc2231Encoder.php new file mode 100644 index 00000000..aa3e062f --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Encoder/Rfc2231Encoder.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Encoder; + +use Symfony\Component\Mime\CharacterStream; + +/** + * @author Chris Corbyn + */ +final class Rfc2231Encoder implements EncoderInterface +{ + /** + * Takes an unencoded string and produces a string encoded according to RFC 2231 from it. + */ + public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string + { + $lines = []; + $lineCount = 0; + $lines[] = ''; + $currentLine = &$lines[$lineCount++]; + + if (0 >= $maxLineLength) { + $maxLineLength = 75; + } + + $charStream = new CharacterStream($string, $charset); + $thisLineLength = $maxLineLength - $firstLineOffset; + + while (null !== $char = $charStream->read(4)) { + $encodedChar = rawurlencode($char); + if (0 !== \strlen($currentLine) && \strlen($currentLine.$encodedChar) > $thisLineLength) { + $lines[] = ''; + $currentLine = &$lines[$lineCount++]; + $thisLineLength = $maxLineLength; + } + $currentLine .= $encodedChar; + } + + return implode("\r\n", $lines); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Exception/AddressEncoderException.php b/lam/lib/3rdParty/composer/symfony/mime/Exception/AddressEncoderException.php new file mode 100644 index 00000000..51ee2e06 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Exception/AddressEncoderException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Exception; + +/** + * @author Fabien Potencier + */ +class AddressEncoderException extends RfcComplianceException +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Exception/ExceptionInterface.php b/lam/lib/3rdParty/composer/symfony/mime/Exception/ExceptionInterface.php new file mode 100644 index 00000000..11933900 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Exception/ExceptionInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Exception; + +/** + * @author Fabien Potencier + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Exception/InvalidArgumentException.php b/lam/lib/3rdParty/composer/symfony/mime/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..e89ebae2 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Exception; + +/** + * @author Fabien Potencier + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Exception/LogicException.php b/lam/lib/3rdParty/composer/symfony/mime/Exception/LogicException.php new file mode 100644 index 00000000..0508ee73 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Exception/LogicException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Exception; + +/** + * @author Fabien Potencier + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Exception/RfcComplianceException.php b/lam/lib/3rdParty/composer/symfony/mime/Exception/RfcComplianceException.php new file mode 100644 index 00000000..26e4a509 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Exception/RfcComplianceException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Exception; + +/** + * @author Fabien Potencier + */ +class RfcComplianceException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Exception/RuntimeException.php b/lam/lib/3rdParty/composer/symfony/mime/Exception/RuntimeException.php new file mode 100644 index 00000000..fb018b00 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Exception; + +/** + * @author Fabien Potencier + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/FileBinaryMimeTypeGuesser.php b/lam/lib/3rdParty/composer/symfony/mime/FileBinaryMimeTypeGuesser.php new file mode 100644 index 00000000..fe1e0cde --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/FileBinaryMimeTypeGuesser.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Exception\LogicException; + +/** + * Guesses the MIME type with the binary "file" (only available on *nix). + * + * @author Bernhard Schussek + */ +class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface +{ + private $cmd; + + /** + * The $cmd pattern must contain a "%s" string that will be replaced + * with the file name to guess. + * + * The command output must start with the MIME type of the file. + * + * @param string $cmd The command to run to get the MIME type of a file + */ + public function __construct(string $cmd = 'file -b --mime -- %s 2>/dev/null') + { + $this->cmd = $cmd; + } + + /** + * {@inheritdoc} + */ + public function isGuesserSupported(): bool + { + static $supported = null; + + if (null !== $supported) { + return $supported; + } + + if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('passthru') || !\function_exists('escapeshellarg')) { + return $supported = false; + } + + ob_start(); + passthru('command -v file', $exitStatus); + $binPath = trim(ob_get_clean()); + + return $supported = 0 === $exitStatus && '' !== $binPath; + } + + /** + * {@inheritdoc} + */ + public function guessMimeType(string $path): ?string + { + if (!is_file($path) || !is_readable($path)) { + throw new InvalidArgumentException(sprintf('The "%s" file does not exist or is not readable.', $path)); + } + + if (!$this->isGuesserSupported()) { + throw new LogicException(sprintf('The "%s" guesser is not supported.', __CLASS__)); + } + + ob_start(); + + // need to use --mime instead of -i. see #6641 + passthru(sprintf($this->cmd, escapeshellarg((0 === strpos($path, '-') ? './' : '').$path)), $return); + if ($return > 0) { + ob_end_clean(); + + return null; + } + + $type = trim(ob_get_clean()); + + if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\+\.]+)#i', $type, $match)) { + // it's not a type, but an error message + return null; + } + + return $match[1]; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/FileinfoMimeTypeGuesser.php b/lam/lib/3rdParty/composer/symfony/mime/FileinfoMimeTypeGuesser.php new file mode 100644 index 00000000..b91a4ffe --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/FileinfoMimeTypeGuesser.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Exception\LogicException; + +/** + * Guesses the MIME type using the PECL extension FileInfo. + * + * @author Bernhard Schussek + */ +class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface +{ + private $magicFile; + + /** + * @param string $magicFile A magic file to use with the finfo instance + * + * @see http://www.php.net/manual/en/function.finfo-open.php + */ + public function __construct(string $magicFile = null) + { + $this->magicFile = $magicFile; + } + + /** + * {@inheritdoc} + */ + public function isGuesserSupported(): bool + { + return \function_exists('finfo_open'); + } + + /** + * {@inheritdoc} + */ + public function guessMimeType(string $path): ?string + { + if (!is_file($path) || !is_readable($path)) { + throw new InvalidArgumentException(sprintf('The "%s" file does not exist or is not readable.', $path)); + } + + if (!$this->isGuesserSupported()) { + throw new LogicException(sprintf('The "%s" guesser is not supported.', __CLASS__)); + } + + if (false === $finfo = new \finfo(FILEINFO_MIME_TYPE, $this->magicFile)) { + return null; + } + + return $finfo->file($path); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Header/AbstractHeader.php b/lam/lib/3rdParty/composer/symfony/mime/Header/AbstractHeader.php new file mode 100644 index 00000000..548c1926 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Header/AbstractHeader.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +use Symfony\Component\Mime\Encoder\QpMimeHeaderEncoder; + +/** + * An abstract base MIME Header. + * + * @author Chris Corbyn + */ +abstract class AbstractHeader implements HeaderInterface +{ + const PHRASE_PATTERN = '(?:(?:(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]+(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?)|(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?"((?:(?:[ \t]*(?:\r\n))?[ \t])?(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21\x23-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])))*(?:(?:[ \t]*(?:\r\n))?[ \t])?"(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?))+?)'; + + private static $encoder; + + private $name; + private $lineLength = 76; + private $lang; + private $charset = 'utf-8'; + + public function __construct(string $name) + { + $this->name = $name; + } + + public function setCharset(string $charset) + { + $this->charset = $charset; + } + + public function getCharset(): ?string + { + return $this->charset; + } + + /** + * Set the language used in this Header. + * + * For example, for US English, 'en-us'. + */ + public function setLanguage(string $lang) + { + $this->lang = $lang; + } + + public function getLanguage(): ?string + { + return $this->lang; + } + + public function getName(): string + { + return $this->name; + } + + public function setMaxLineLength(int $lineLength) + { + $this->lineLength = $lineLength; + } + + public function getMaxLineLength(): int + { + return $this->lineLength; + } + + public function toString(): string + { + return $this->tokensToString($this->toTokens()); + } + + /** + * Produces a compliant, formatted RFC 2822 'phrase' based on the string given. + * + * @param string $string as displayed + * @param bool $shorten the first line to make remove for header name + */ + protected function createPhrase(HeaderInterface $header, string $string, string $charset, bool $shorten = false): string + { + // Treat token as exactly what was given + $phraseStr = $string; + + // If it's not valid + if (!preg_match('/^'.self::PHRASE_PATTERN.'$/D', $phraseStr)) { + // .. but it is just ascii text, try escaping some characters + // and make it a quoted-string + if (preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $phraseStr)) { + foreach (['\\', '"'] as $char) { + $phraseStr = str_replace($char, '\\'.$char, $phraseStr); + } + $phraseStr = '"'.$phraseStr.'"'; + } else { + // ... otherwise it needs encoding + // Determine space remaining on line if first line + if ($shorten) { + $usedLength = \strlen($header->getName().': '); + } else { + $usedLength = 0; + } + $phraseStr = $this->encodeWords($header, $string, $usedLength); + } + } + + return $phraseStr; + } + + /** + * Encode needed word tokens within a string of input. + */ + protected function encodeWords(HeaderInterface $header, string $input, int $usedLength = -1): string + { + $value = ''; + $tokens = $this->getEncodableWordTokens($input); + foreach ($tokens as $token) { + // See RFC 2822, Sect 2.2 (really 2.2 ??) + if ($this->tokenNeedsEncoding($token)) { + // Don't encode starting WSP + $firstChar = substr($token, 0, 1); + switch ($firstChar) { + case ' ': + case "\t": + $value .= $firstChar; + $token = substr($token, 1); + } + + if (-1 == $usedLength) { + $usedLength = \strlen($header->getName().': ') + \strlen($value); + } + $value .= $this->getTokenAsEncodedWord($token, $usedLength); + } else { + $value .= $token; + } + } + + return $value; + } + + protected function tokenNeedsEncoding(string $token): bool + { + return (bool) preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token); + } + + /** + * Splits a string into tokens in blocks of words which can be encoded quickly. + * + * @return string[] + */ + protected function getEncodableWordTokens(string $string): array + { + $tokens = []; + $encodedToken = ''; + // Split at all whitespace boundaries + foreach (preg_split('~(?=[\t ])~', $string) as $token) { + if ($this->tokenNeedsEncoding($token)) { + $encodedToken .= $token; + } else { + if (\strlen($encodedToken) > 0) { + $tokens[] = $encodedToken; + $encodedToken = ''; + } + $tokens[] = $token; + } + } + if (\strlen($encodedToken)) { + $tokens[] = $encodedToken; + } + + return $tokens; + } + + /** + * Get a token as an encoded word for safe insertion into headers. + */ + protected function getTokenAsEncodedWord(string $token, int $firstLineOffset = 0): string + { + if (null === self::$encoder) { + self::$encoder = new QpMimeHeaderEncoder(); + } + + // Adjust $firstLineOffset to account for space needed for syntax + $charsetDecl = $this->charset; + if (null !== $this->lang) { + $charsetDecl .= '*'.$this->lang; + } + $encodingWrapperLength = \strlen('=?'.$charsetDecl.'?'.self::$encoder->getName().'??='); + + if ($firstLineOffset >= 75) { + //Does this logic need to be here? + $firstLineOffset = 0; + } + + $encodedTextLines = explode("\r\n", + self::$encoder->encodeString($token, $this->charset, $firstLineOffset, 75 - $encodingWrapperLength) + ); + + if ('iso-2022-jp' !== strtolower($this->charset)) { + // special encoding for iso-2022-jp using mb_encode_mimeheader + foreach ($encodedTextLines as $lineNum => $line) { + $encodedTextLines[$lineNum] = '=?'.$charsetDecl.'?'.self::$encoder->getName().'?'.$line.'?='; + } + } + + return implode("\r\n ", $encodedTextLines); + } + + /** + * Generates tokens from the given string which include CRLF as individual tokens. + * + * @return string[] + */ + protected function generateTokenLines(string $token): array + { + return preg_split('~(\r\n)~', $token, -1, PREG_SPLIT_DELIM_CAPTURE); + } + + /** + * Generate a list of all tokens in the final header. + */ + protected function toTokens(string $string = null): array + { + if (null === $string) { + $string = $this->getBodyAsString(); + } + + $tokens = []; + // Generate atoms; split at all invisible boundaries followed by WSP + foreach (preg_split('~(?=[ \t])~', $string) as $token) { + $newTokens = $this->generateTokenLines($token); + foreach ($newTokens as $newToken) { + $tokens[] = $newToken; + } + } + + return $tokens; + } + + /** + * Takes an array of tokens which appear in the header and turns them into + * an RFC 2822 compliant string, adding FWSP where needed. + * + * @param string[] $tokens + */ + private function tokensToString(array $tokens): string + { + $lineCount = 0; + $headerLines = []; + $headerLines[] = $this->name.': '; + $currentLine = &$headerLines[$lineCount++]; + + // Build all tokens back into compliant header + foreach ($tokens as $i => $token) { + // Line longer than specified maximum or token was just a new line + if (("\r\n" === $token) || + ($i > 0 && \strlen($currentLine.$token) > $this->lineLength) + && 0 < \strlen($currentLine)) { + $headerLines[] = ''; + $currentLine = &$headerLines[$lineCount++]; + } + + // Append token to the line + if ("\r\n" !== $token) { + $currentLine .= $token; + } + } + + // Implode with FWS (RFC 2822, 2.2.3) + return implode("\r\n", $headerLines); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Header/DateHeader.php b/lam/lib/3rdParty/composer/symfony/mime/Header/DateHeader.php new file mode 100644 index 00000000..a7385d4c --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Header/DateHeader.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +/** + * A Date MIME Header. + * + * @author Chris Corbyn + */ +final class DateHeader extends AbstractHeader +{ + private $dateTime; + + public function __construct(string $name, \DateTimeInterface $date) + { + parent::__construct($name); + + $this->setDateTime($date); + } + + /** + * @param \DateTimeInterface $body + */ + public function setBody($body) + { + $this->setDateTime($body); + } + + public function getBody(): \DateTimeImmutable + { + return $this->getDateTime(); + } + + public function getDateTime(): \DateTimeImmutable + { + return $this->dateTime; + } + + /** + * Set the date-time of the Date in this Header. + * + * If a DateTime instance is provided, it is converted to DateTimeImmutable. + */ + public function setDateTime(\DateTimeInterface $dateTime) + { + if ($dateTime instanceof \DateTime) { + $immutable = new \DateTimeImmutable('@'.$dateTime->getTimestamp()); + $dateTime = $immutable->setTimezone($dateTime->getTimezone()); + } + $this->dateTime = $dateTime; + } + + public function getBodyAsString(): string + { + return $this->dateTime->format(\DateTime::RFC2822); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Header/HeaderInterface.php b/lam/lib/3rdParty/composer/symfony/mime/Header/HeaderInterface.php new file mode 100644 index 00000000..4546947c --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Header/HeaderInterface.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +/** + * A MIME Header. + * + * @author Chris Corbyn + */ +interface HeaderInterface +{ + /** + * Sets the body. + * + * The type depends on the Header concrete class. + * + * @param mixed $body + */ + public function setBody($body); + + /** + * Gets the body. + * + * The return type depends on the Header concrete class. + * + * @return mixed + */ + public function getBody(); + + public function setCharset(string $charset); + + public function getCharset(): ?string; + + public function setLanguage(string $lang); + + public function getLanguage(): ?string; + + public function getName(): string; + + public function setMaxLineLength(int $lineLength); + + public function getMaxLineLength(): int; + + /** + * Gets this Header rendered as a compliant string. + */ + public function toString(): string; + + /** + * Gets the header's body, prepared for folding into a final header value. + * + * This is not necessarily RFC 2822 compliant since folding white space is + * not added at this stage (see {@link toString()} for that). + */ + public function getBodyAsString(): string; +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Header/Headers.php b/lam/lib/3rdParty/composer/symfony/mime/Header/Headers.php new file mode 100644 index 00000000..9de506e3 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Header/Headers.php @@ -0,0 +1,282 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Exception\LogicException; + +/** + * A collection of headers. + * + * @author Fabien Potencier + */ +final class Headers +{ + private static $uniqueHeaders = [ + 'date', 'from', 'sender', 'reply-to', 'to', 'cc', 'bcc', + 'message-id', 'in-reply-to', 'references', 'subject', + ]; + + private $headers = []; + private $lineLength = 76; + + public function __construct(HeaderInterface ...$headers) + { + foreach ($headers as $header) { + $this->add($header); + } + } + + public function __clone() + { + foreach ($this->headers as $name => $collection) { + foreach ($collection as $i => $header) { + $this->headers[$name][$i] = clone $header; + } + } + } + + public function setMaxLineLength(int $lineLength) + { + $this->lineLength = $lineLength; + foreach ($this->all() as $header) { + $header->setMaxLineLength($lineLength); + } + } + + public function getMaxLineLength(): int + { + return $this->lineLength; + } + + /** + * @param (Address|string)[] $addresses + * + * @return $this + */ + public function addMailboxListHeader(string $name, array $addresses): self + { + return $this->add(new MailboxListHeader($name, Address::createArray($addresses))); + } + + /** + * @param Address|string $address + * + * @return $this + */ + public function addMailboxHeader(string $name, $address): self + { + return $this->add(new MailboxHeader($name, Address::create($address))); + } + + /** + * @param string|array $ids + * + * @return $this + */ + public function addIdHeader(string $name, $ids): self + { + return $this->add(new IdentificationHeader($name, $ids)); + } + + /** + * @param Address|string $path + * + * @return $this + */ + public function addPathHeader(string $name, $path): self + { + return $this->add(new PathHeader($name, $path instanceof Address ? $path : new Address($path))); + } + + /** + * @return $this + */ + public function addDateHeader(string $name, \DateTimeInterface $dateTime): self + { + return $this->add(new DateHeader($name, $dateTime)); + } + + /** + * @return $this + */ + public function addTextHeader(string $name, string $value): self + { + return $this->add(new UnstructuredHeader($name, $value)); + } + + /** + * @return $this + */ + public function addParameterizedHeader(string $name, string $value, array $params = []): self + { + return $this->add(new ParameterizedHeader($name, $value, $params)); + } + + public function has(string $name): bool + { + return isset($this->headers[strtolower($name)]); + } + + /** + * @return $this + */ + public function add(HeaderInterface $header): self + { + static $map = [ + 'date' => DateHeader::class, + 'from' => MailboxListHeader::class, + 'sender' => MailboxHeader::class, + 'reply-to' => MailboxListHeader::class, + 'to' => MailboxListHeader::class, + 'cc' => MailboxListHeader::class, + 'bcc' => MailboxListHeader::class, + 'message-id' => IdentificationHeader::class, + 'in-reply-to' => IdentificationHeader::class, + 'references' => IdentificationHeader::class, + 'return-path' => PathHeader::class, + ]; + + $header->setMaxLineLength($this->lineLength); + $name = strtolower($header->getName()); + + if (isset($map[$name]) && !$header instanceof $map[$name]) { + throw new LogicException(sprintf('The "%s" header must be an instance of "%s" (got "%s").', $header->getName(), $map[$name], \get_class($header))); + } + + if (\in_array($name, self::$uniqueHeaders, true) && isset($this->headers[$name]) && \count($this->headers[$name]) > 0) { + throw new LogicException(sprintf('Impossible to set header "%s" as it\'s already defined and must be unique.', $header->getName())); + } + + $this->headers[$name][] = $header; + + return $this; + } + + public function get(string $name): ?HeaderInterface + { + $name = strtolower($name); + if (!isset($this->headers[$name])) { + return null; + } + + $values = array_values($this->headers[$name]); + + return array_shift($values); + } + + public function all(string $name = null): iterable + { + if (null === $name) { + foreach ($this->headers as $name => $collection) { + foreach ($collection as $header) { + yield $name => $header; + } + } + } elseif (isset($this->headers[strtolower($name)])) { + foreach ($this->headers[strtolower($name)] as $header) { + yield $header; + } + } + } + + public function getNames(): array + { + return array_keys($this->headers); + } + + public function remove(string $name): void + { + unset($this->headers[strtolower($name)]); + } + + public static function isUniqueHeader(string $name): bool + { + return \in_array($name, self::$uniqueHeaders, true); + } + + public function toString(): string + { + $string = ''; + foreach ($this->toArray() as $str) { + $string .= $str."\r\n"; + } + + return $string; + } + + public function toArray(): array + { + $arr = []; + foreach ($this->all() as $header) { + if ('' !== $header->getBodyAsString()) { + $arr[] = $header->toString(); + } + } + + return $arr; + } + + /** + * @internal + */ + public function getHeaderBody($name) + { + return $this->has($name) ? $this->get($name)->getBody() : null; + } + + /** + * @internal + */ + public function setHeaderBody(string $type, string $name, $body): void + { + if ($this->has($name)) { + $this->get($name)->setBody($body); + } else { + $this->{'add'.$type.'Header'}($name, $body); + } + } + + /** + * @internal + */ + public function getHeaderParameter(string $name, string $parameter): ?string + { + if (!$this->has($name)) { + return null; + } + + $header = $this->get($name); + if (!$header instanceof ParameterizedHeader) { + throw new LogicException(sprintf('Unable to get parameter "%s" on header "%s" as the header is not of class "%s".', $parameter, $name, ParameterizedHeader::class)); + } + + return $header->getParameter($parameter); + } + + /** + * @internal + */ + public function setHeaderParameter(string $name, string $parameter, $value): void + { + if (!$this->has($name)) { + throw new LogicException(sprintf('Unable to set parameter "%s" on header "%s" as the header is not defined.', $parameter, $name)); + } + + $header = $this->get($name); + if (!$header instanceof ParameterizedHeader) { + throw new LogicException(sprintf('Unable to set parameter "%s" on header "%s" as the header is not of class "%s".', $parameter, $name, ParameterizedHeader::class)); + } + + $header->setParameter($parameter, $value); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Header/IdentificationHeader.php b/lam/lib/3rdParty/composer/symfony/mime/Header/IdentificationHeader.php new file mode 100644 index 00000000..8a94574e --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Header/IdentificationHeader.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Exception\RfcComplianceException; + +/** + * An ID MIME Header for something like Message-ID or Content-ID (one or more addresses). + * + * @author Chris Corbyn + */ +final class IdentificationHeader extends AbstractHeader +{ + private $ids = []; + private $idsAsAddresses = []; + + /** + * @param string|array $ids + */ + public function __construct(string $name, $ids) + { + parent::__construct($name); + + $this->setId($ids); + } + + /** + * @param string|array $body a string ID or an array of IDs + * + * @throws RfcComplianceException + */ + public function setBody($body) + { + $this->setId($body); + } + + public function getBody(): array + { + return $this->getIds(); + } + + /** + * Set the ID used in the value of this header. + * + * @param string|array $id + * + * @throws RfcComplianceException + */ + public function setId($id) + { + $this->setIds(\is_array($id) ? $id : [$id]); + } + + /** + * Get the ID used in the value of this Header. + * + * If multiple IDs are set only the first is returned. + */ + public function getId(): ?string + { + return $this->ids[0] ?? null; + } + + /** + * Set a collection of IDs to use in the value of this Header. + * + * @param string[] $ids + * + * @throws RfcComplianceException + */ + public function setIds(array $ids) + { + $this->ids = []; + $this->idsAsAddresses = []; + foreach ($ids as $id) { + $this->idsAsAddresses[] = new Address($id); + $this->ids[] = $id; + } + } + + /** + * Get the list of IDs used in this Header. + * + * @return string[] + */ + public function getIds(): array + { + return $this->ids; + } + + public function getBodyAsString(): string + { + $addrs = []; + foreach ($this->idsAsAddresses as $address) { + $addrs[] = '<'.$address->toString().'>'; + } + + return implode(' ', $addrs); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Header/MailboxHeader.php b/lam/lib/3rdParty/composer/symfony/mime/Header/MailboxHeader.php new file mode 100644 index 00000000..b58c8252 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Header/MailboxHeader.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Exception\RfcComplianceException; + +/** + * A Mailbox MIME Header for something like Sender (one named address). + * + * @author Fabien Potencier + */ +final class MailboxHeader extends AbstractHeader +{ + private $address; + + public function __construct(string $name, Address $address) + { + parent::__construct($name); + + $this->setAddress($address); + } + + /** + * @param Address $body + * + * @throws RfcComplianceException + */ + public function setBody($body) + { + $this->setAddress($body); + } + + /** + * @throws RfcComplianceException + */ + public function getBody(): Address + { + return $this->getAddress(); + } + + /** + * @throws RfcComplianceException + */ + public function setAddress(Address $address) + { + $this->address = $address; + } + + public function getAddress(): Address + { + return $this->address; + } + + public function getBodyAsString(): string + { + $str = $this->address->getEncodedAddress(); + if ($name = $this->address->getName()) { + $str = $this->createPhrase($this, $name, $this->getCharset(), true).' <'.$str.'>'; + } + + return $str; + } + + /** + * Redefine the encoding requirements for an address. + * + * All "specials" must be encoded as the full header value will not be quoted + * + * @see RFC 2822 3.2.1 + */ + protected function tokenNeedsEncoding(string $token): bool + { + return preg_match('/[()<>\[\]:;@\,."]/', $token) || parent::tokenNeedsEncoding($token); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Header/MailboxListHeader.php b/lam/lib/3rdParty/composer/symfony/mime/Header/MailboxListHeader.php new file mode 100644 index 00000000..1d00fdb1 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Header/MailboxListHeader.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Exception\RfcComplianceException; + +/** + * A Mailbox list MIME Header for something like From, To, Cc, and Bcc (one or more named addresses). + * + * @author Chris Corbyn + */ +final class MailboxListHeader extends AbstractHeader +{ + private $addresses = []; + + /** + * @param Address[] $addresses + */ + public function __construct(string $name, array $addresses) + { + parent::__construct($name); + + $this->setAddresses($addresses); + } + + /** + * @param Address[] $body + * + * @throws RfcComplianceException + */ + public function setBody($body) + { + $this->setAddresses($body); + } + + /** + * @throws RfcComplianceException + * + * @return Address[] + */ + public function getBody(): array + { + return $this->getAddresses(); + } + + /** + * Sets a list of addresses to be shown in this Header. + * + * @param Address[] $addresses + * + * @throws RfcComplianceException + */ + public function setAddresses(array $addresses) + { + $this->addresses = []; + $this->addAddresses($addresses); + } + + /** + * Sets a list of addresses to be shown in this Header. + * + * @param Address[] $addresses + * + * @throws RfcComplianceException + */ + public function addAddresses(array $addresses) + { + foreach ($addresses as $address) { + $this->addAddress($address); + } + } + + /** + * @throws RfcComplianceException + */ + public function addAddress(Address $address) + { + $this->addresses[] = $address; + } + + /** + * @return Address[] + */ + public function getAddresses(): array + { + return $this->addresses; + } + + /** + * Gets the full mailbox list of this Header as an array of valid RFC 2822 strings. + * + * @throws RfcComplianceException + * + * @return string[] + */ + public function getAddressStrings(): array + { + $strings = []; + foreach ($this->addresses as $address) { + $str = $address->getEncodedAddress(); + if ($name = $address->getName()) { + $str = $this->createPhrase($this, $name, $this->getCharset(), !$strings).' <'.$str.'>'; + } + $strings[] = $str; + } + + return $strings; + } + + public function getBodyAsString(): string + { + return implode(', ', $this->getAddressStrings()); + } + + /** + * Redefine the encoding requirements for addresses. + * + * All "specials" must be encoded as the full header value will not be quoted + * + * @see RFC 2822 3.2.1 + */ + protected function tokenNeedsEncoding(string $token): bool + { + return preg_match('/[()<>\[\]:;@\,."]/', $token) || parent::tokenNeedsEncoding($token); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Header/ParameterizedHeader.php b/lam/lib/3rdParty/composer/symfony/mime/Header/ParameterizedHeader.php new file mode 100644 index 00000000..d8e50011 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Header/ParameterizedHeader.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +use Symfony\Component\Mime\Encoder\Rfc2231Encoder; + +/** + * @author Chris Corbyn + */ +final class ParameterizedHeader extends UnstructuredHeader +{ + /** + * RFC 2231's definition of a token. + * + * @var string + */ + const TOKEN_REGEX = '(?:[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+)'; + + private $encoder; + private $parameters = []; + + public function __construct(string $name, string $value, array $parameters = []) + { + parent::__construct($name, $value); + + foreach ($parameters as $k => $v) { + $this->setParameter($k, $v); + } + + if ('content-type' !== strtolower($name)) { + $this->encoder = new Rfc2231Encoder(); + } + } + + public function setParameter(string $parameter, ?string $value) + { + $this->setParameters(array_merge($this->getParameters(), [$parameter => $value])); + } + + public function getParameter(string $parameter): string + { + return $this->getParameters()[$parameter] ?? ''; + } + + /** + * @param string[] $parameters + */ + public function setParameters(array $parameters) + { + $this->parameters = $parameters; + } + + /** + * @return string[] + */ + public function getParameters(): array + { + return $this->parameters; + } + + public function getBodyAsString(): string + { + $body = parent::getBodyAsString(); + foreach ($this->parameters as $name => $value) { + if (null !== $value) { + $body .= '; '.$this->createParameter($name, $value); + } + } + + return $body; + } + + /** + * Generate a list of all tokens in the final header. + * + * This doesn't need to be overridden in theory, but it is for implementation + * reasons to prevent potential breakage of attributes. + */ + protected function toTokens(string $string = null): array + { + $tokens = parent::toTokens(parent::getBodyAsString()); + + // Try creating any parameters + foreach ($this->parameters as $name => $value) { + if (null !== $value) { + // Add the semi-colon separator + $tokens[\count($tokens) - 1] .= ';'; + $tokens = array_merge($tokens, $this->generateTokenLines(' '.$this->createParameter($name, $value))); + } + } + + return $tokens; + } + + /** + * Render a RFC 2047 compliant header parameter from the $name and $value. + */ + private function createParameter(string $name, string $value): string + { + $origValue = $value; + + $encoded = false; + // Allow room for parameter name, indices, "=" and DQUOTEs + $maxValueLength = $this->getMaxLineLength() - \strlen($name.'=*N"";') - 1; + $firstLineOffset = 0; + + // If it's not already a valid parameter value... + if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) { + // TODO: text, or something else?? + // ... and it's not ascii + if (!preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $value)) { + $encoded = true; + // Allow space for the indices, charset and language + $maxValueLength = $this->getMaxLineLength() - \strlen($name.'*N*="";') - 1; + $firstLineOffset = \strlen($this->getCharset()."'".$this->getLanguage()."'"); + } + } + + // Encode if we need to + if ($encoded || \strlen($value) > $maxValueLength) { + if (null !== $this->encoder) { + $value = $this->encoder->encodeString($origValue, $this->getCharset(), $firstLineOffset, $maxValueLength); + } else { + // We have to go against RFC 2183/2231 in some areas for interoperability + $value = $this->getTokenAsEncodedWord($origValue); + $encoded = false; + } + } + + $valueLines = $this->encoder ? explode("\r\n", $value) : [$value]; + + // Need to add indices + if (\count($valueLines) > 1) { + $paramLines = []; + foreach ($valueLines as $i => $line) { + $paramLines[] = $name.'*'.$i.$this->getEndOfParameterValue($line, true, 0 === $i); + } + + return implode(";\r\n ", $paramLines); + } else { + return $name.$this->getEndOfParameterValue($valueLines[0], $encoded, true); + } + } + + /** + * Returns the parameter value from the "=" and beyond. + * + * @param string $value to append + */ + private function getEndOfParameterValue(string $value, bool $encoded = false, bool $firstLine = false): string + { + if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) { + $value = '"'.$value.'"'; + } + $prepend = '='; + if ($encoded) { + $prepend = '*='; + if ($firstLine) { + $prepend = '*='.$this->getCharset()."'".$this->getLanguage()."'"; + } + } + + return $prepend.$value; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Header/PathHeader.php b/lam/lib/3rdParty/composer/symfony/mime/Header/PathHeader.php new file mode 100644 index 00000000..5101ad0f --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Header/PathHeader.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Exception\RfcComplianceException; + +/** + * A Path Header, such a Return-Path (one address). + * + * @author Chris Corbyn + */ +final class PathHeader extends AbstractHeader +{ + private $address; + + public function __construct(string $name, Address $address) + { + parent::__construct($name); + + $this->setAddress($address); + } + + /** + * @param Address $body + * + * @throws RfcComplianceException + */ + public function setBody($body) + { + $this->setAddress($body); + } + + public function getBody(): Address + { + return $this->getAddress(); + } + + public function setAddress(Address $address) + { + $this->address = $address; + } + + public function getAddress(): Address + { + return $this->address; + } + + public function getBodyAsString(): string + { + return '<'.$this->address->toString().'>'; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Header/UnstructuredHeader.php b/lam/lib/3rdParty/composer/symfony/mime/Header/UnstructuredHeader.php new file mode 100644 index 00000000..2085ddfd --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Header/UnstructuredHeader.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Header; + +/** + * A Simple MIME Header. + * + * @author Chris Corbyn + */ +class UnstructuredHeader extends AbstractHeader +{ + private $value; + + public function __construct(string $name, string $value) + { + parent::__construct($name); + + $this->setValue($value); + } + + /** + * @param string $body + */ + public function setBody($body) + { + $this->setValue($body); + } + + /** + * @return string + */ + public function getBody() + { + return $this->getValue(); + } + + /** + * Get the (unencoded) value of this header. + */ + public function getValue(): string + { + return $this->value; + } + + /** + * Set the (unencoded) value of this header. + */ + public function setValue(string $value) + { + $this->value = $value; + } + + /** + * Get the value of this header prepared for rendering. + */ + public function getBodyAsString(): string + { + return $this->encodeWords($this, $this->value); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/LICENSE b/lam/lib/3rdParty/composer/symfony/mime/LICENSE new file mode 100644 index 00000000..9a9a61b1 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lam/lib/3rdParty/composer/symfony/mime/Message.php b/lam/lib/3rdParty/composer/symfony/mime/Message.php new file mode 100644 index 00000000..5b4e67f1 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Message.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Exception\LogicException; +use Symfony\Component\Mime\Header\Headers; +use Symfony\Component\Mime\Part\AbstractPart; +use Symfony\Component\Mime\Part\TextPart; + +/** + * @author Fabien Potencier + */ +class Message extends RawMessage +{ + private $headers; + private $body; + + public function __construct(Headers $headers = null, AbstractPart $body = null) + { + $this->headers = $headers ? clone $headers : new Headers(); + $this->body = $body; + } + + public function __clone() + { + $this->headers = clone $this->headers; + + if (null !== $this->body) { + $this->body = clone $this->body; + } + } + + /** + * @return $this + */ + public function setBody(AbstractPart $body = null) + { + $this->body = $body; + + return $this; + } + + public function getBody(): ?AbstractPart + { + return $this->body; + } + + /** + * @return $this + */ + public function setHeaders(Headers $headers) + { + $this->headers = $headers; + + return $this; + } + + public function getHeaders(): Headers + { + return $this->headers; + } + + public function getPreparedHeaders(): Headers + { + $headers = clone $this->headers; + + if (!$headers->has('From')) { + throw new LogicException('An email must have a "From" header.'); + } + + $headers->addTextHeader('MIME-Version', '1.0'); + + if (!$headers->has('Date')) { + $headers->addDateHeader('Date', new \DateTimeImmutable()); + } + + // determine the "real" sender + if (!$headers->has('Sender') && \count($froms = $headers->get('From')->getAddresses()) > 1) { + $headers->addMailboxHeader('Sender', $froms[0]); + } + + if (!$headers->has('Message-ID')) { + $headers->addIdHeader('Message-ID', $this->generateMessageId()); + } + + // remove the Bcc field which should NOT be part of the sent message + $headers->remove('Bcc'); + + return $headers; + } + + public function toString(): string + { + if (null === $body = $this->getBody()) { + $body = new TextPart(''); + } + + return $this->getPreparedHeaders()->toString().$body->toString(); + } + + public function toIterable(): iterable + { + if (null === $body = $this->getBody()) { + $body = new TextPart(''); + } + + yield $this->getPreparedHeaders()->toString(); + yield from $body->toIterable(); + } + + public function ensureValidity() + { + if (!$this->headers->has('From')) { + throw new LogicException('An email must have a "From" header.'); + } + + parent::ensureValidity(); + } + + public function generateMessageId(): string + { + if ($this->headers->has('Sender')) { + $sender = $this->headers->get('Sender')->getAddress(); + } elseif ($this->headers->has('From')) { + $sender = $this->headers->get('From')->getAddresses()[0]; + } else { + throw new LogicException('An email must have a "From" or a "Sender" header to compute a Messsage ID.'); + } + + return bin2hex(random_bytes(16)).strstr($sender->getAddress(), '@'); + } + + public function __serialize(): array + { + return [$this->headers, $this->body]; + } + + public function __unserialize(array $data): void + { + [$this->headers, $this->body] = $data; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/MessageConverter.php b/lam/lib/3rdParty/composer/symfony/mime/MessageConverter.php new file mode 100644 index 00000000..a810cb70 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/MessageConverter.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Exception\RuntimeException; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\Multipart\AlternativePart; +use Symfony\Component\Mime\Part\Multipart\MixedPart; +use Symfony\Component\Mime\Part\Multipart\RelatedPart; +use Symfony\Component\Mime\Part\TextPart; + +/** + * @author Fabien Potencier + */ +final class MessageConverter +{ + /** + * @throws RuntimeException when unable to convert the message to an email + */ + public static function toEmail(Message $message): Email + { + if ($message instanceof Email) { + return $message; + } + + // try to convert to a "simple" Email instance + $body = $message->getBody(); + if ($body instanceof TextPart) { + return self::createEmailFromTextPart($message, $body); + } + + if ($body instanceof AlternativePart) { + return self::createEmailFromAlternativePart($message, $body); + } + + if ($body instanceof RelatedPart) { + return self::createEmailFromRelatedPart($message, $body); + } + + if ($body instanceof MixedPart) { + $parts = $body->getParts(); + if ($parts[0] instanceof RelatedPart) { + $email = self::createEmailFromRelatedPart($message, $parts[0]); + } elseif ($parts[0] instanceof AlternativePart) { + $email = self::createEmailFromAlternativePart($message, $parts[0]); + } elseif ($parts[0] instanceof TextPart) { + $email = self::createEmailFromTextPart($message, $parts[0]); + } else { + throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', \get_class($message))); + } + + return self::attachParts($email, \array_slice($parts, 1)); + } + + throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', \get_class($message))); + } + + private static function createEmailFromTextPart(Message $message, TextPart $part): Email + { + if ('text' === $part->getMediaType() && 'plain' === $part->getMediaSubtype()) { + return (new Email(clone $message->getHeaders()))->text($part->getBody(), $part->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8'); + } + if ('text' === $part->getMediaType() && 'html' === $part->getMediaSubtype()) { + return (new Email(clone $message->getHeaders()))->html($part->getBody(), $part->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8'); + } + + throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', \get_class($message))); + } + + private static function createEmailFromAlternativePart(Message $message, AlternativePart $part): Email + { + $parts = $part->getParts(); + if ( + 2 === \count($parts) && + $parts[0] instanceof TextPart && 'text' === $parts[0]->getMediaType() && 'plain' === $parts[0]->getMediaSubtype() && + $parts[1] instanceof TextPart && 'text' === $parts[1]->getMediaType() && 'html' === $parts[1]->getMediaSubtype() + ) { + return (new Email(clone $message->getHeaders())) + ->text($parts[0]->getBody(), $parts[0]->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8') + ->html($parts[1]->getBody(), $parts[1]->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8') + ; + } + + throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', \get_class($message))); + } + + private static function createEmailFromRelatedPart(Message $message, RelatedPart $part): Email + { + $parts = $part->getParts(); + if ($parts[0] instanceof AlternativePart) { + $email = self::createEmailFromAlternativePart($message, $parts[0]); + } elseif ($parts[0] instanceof TextPart) { + $email = self::createEmailFromTextPart($message, $parts[0]); + } else { + throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', \get_class($message))); + } + + return self::attachParts($email, \array_slice($parts, 1)); + } + + private static function attachParts(Email $email, array $parts): Email + { + foreach ($parts as $part) { + if (!$part instanceof DataPart) { + throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', \get_class($email))); + } + + $headers = $part->getPreparedHeaders(); + $method = 'inline' === $headers->getHeaderBody('Content-Disposition') ? 'embed' : 'attach'; + $name = $headers->getHeaderParameter('Content-Disposition', 'filename'); + $email->$method($part->getBody(), $name, $part->getMediaType().'/'.$part->getMediaSubtype()); + } + + return $email; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/MimeTypeGuesserInterface.php b/lam/lib/3rdParty/composer/symfony/mime/MimeTypeGuesserInterface.php new file mode 100644 index 00000000..68b05055 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/MimeTypeGuesserInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +/** + * Guesses the MIME type of a file. + * + * @author Fabien Potencier + */ +interface MimeTypeGuesserInterface +{ + /** + * Returns true if this guesser is supported. + */ + public function isGuesserSupported(): bool; + + /** + * Guesses the MIME type of the file with the given path. + * + * @param string $path The path to the file + * + * @return string|null The MIME type or null, if none could be guessed + * + * @throws \LogicException If the guesser is not supported + * @throws \InvalidArgumentException If the file does not exist or is not readable + */ + public function guessMimeType(string $path): ?string; +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/MimeTypes.php b/lam/lib/3rdParty/composer/symfony/mime/MimeTypes.php new file mode 100644 index 00000000..268658d1 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/MimeTypes.php @@ -0,0 +1,3153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Exception\LogicException; + +/** + * Manages MIME types and file extensions. + * + * For MIME type guessing, you can register custom guessers + * by calling the registerGuesser() method. + * Custom guessers are always called before any default ones: + * + * $guesser = new MimeTypes(); + * $guesser->registerGuesser(new MyCustomMimeTypeGuesser()); + * + * If you want to change the order of the default guessers, just re-register your + * preferred one as a custom one. The last registered guesser is preferred over + * previously registered ones. + * + * Re-registering a built-in guesser also allows you to configure it: + * + * $guesser = new MimeTypes(); + * $guesser->registerGuesser(new FileinfoMimeTypeGuesser('/path/to/magic/file')); + * + * @author Fabien Potencier + */ +final class MimeTypes implements MimeTypesInterface +{ + private $extensions = []; + private $mimeTypes = []; + + /** + * @var MimeTypeGuesserInterface[] + */ + private $guessers = []; + private static $default; + + public function __construct(array $map = []) + { + foreach ($map as $mimeType => $extensions) { + $this->extensions[$mimeType] = $extensions; + + foreach ($extensions as $extension) { + $this->mimeTypes[$extension] = $mimeType; + } + } + $this->registerGuesser(new FileBinaryMimeTypeGuesser()); + $this->registerGuesser(new FileinfoMimeTypeGuesser()); + } + + public static function setDefault(self $default) + { + self::$default = $default; + } + + public static function getDefault(): self + { + return self::$default ?? self::$default = new self(); + } + + /** + * Registers a MIME type guesser. + * + * The last registered guesser has precedence over the other ones. + */ + public function registerGuesser(MimeTypeGuesserInterface $guesser) + { + array_unshift($this->guessers, $guesser); + } + + /** + * {@inheritdoc} + */ + public function getExtensions(string $mimeType): array + { + if ($this->extensions) { + $extensions = $this->extensions[$mimeType] ?? $this->extensions[$lcMimeType = strtolower($mimeType)] ?? null; + } + + return $extensions ?? self::$map[$mimeType] ?? self::$map[$lcMimeType ?? strtolower($mimeType)] ?? []; + } + + /** + * {@inheritdoc} + */ + public function getMimeTypes(string $ext): array + { + if ($this->mimeTypes) { + $mimeTypes = $this->mimeTypes[$ext] ?? $this->mimeTypes[$lcExt = strtolower($ext)] ?? null; + } + + return $mimeTypes ?? self::$reverseMap[$ext] ?? self::$reverseMap[$lcExt ?? strtolower($ext)] ?? []; + } + + /** + * {@inheritdoc} + */ + public function isGuesserSupported(): bool + { + foreach ($this->guessers as $guesser) { + if ($guesser->isGuesserSupported()) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + * + * The file is passed to each registered MIME type guesser in reverse order + * of their registration (last registered is queried first). Once a guesser + * returns a value that is not null, this method terminates and returns the + * value. + */ + public function guessMimeType(string $path): ?string + { + foreach ($this->guessers as $guesser) { + if (!$guesser->isGuesserSupported()) { + continue; + } + + if (null !== $mimeType = $guesser->guessMimeType($path)) { + return $mimeType; + } + } + + if (!$this->isGuesserSupported()) { + throw new LogicException('Unable to guess the MIME type as no guessers are available (have you enable the php_fileinfo extension?).'); + } + + return null; + } + + /** + * A map of MIME types and their default extensions. + * + * Updated from upstream on 2019-01-16 + * + * @see Resources/bin/update_mime_types.php + */ + private static $map = [ + 'application/acrobat' => ['pdf'], + 'application/andrew-inset' => ['ez'], + 'application/annodex' => ['anx'], + 'application/applixware' => ['aw'], + 'application/atom+xml' => ['atom'], + 'application/atomcat+xml' => ['atomcat'], + 'application/atomsvc+xml' => ['atomsvc'], + 'application/ccxml+xml' => ['ccxml'], + 'application/cdmi-capability' => ['cdmia'], + 'application/cdmi-container' => ['cdmic'], + 'application/cdmi-domain' => ['cdmid'], + 'application/cdmi-object' => ['cdmio'], + 'application/cdmi-queue' => ['cdmiq'], + 'application/cdr' => ['cdr'], + 'application/coreldraw' => ['cdr'], + 'application/cu-seeme' => ['cu'], + 'application/davmount+xml' => ['davmount'], + 'application/dbase' => ['dbf'], + 'application/dbf' => ['dbf'], + 'application/dicom' => ['dcm'], + 'application/docbook+xml' => ['dbk', 'docbook'], + 'application/dssc+der' => ['dssc'], + 'application/dssc+xml' => ['xdssc'], + 'application/ecmascript' => ['ecma', 'es'], + 'application/emf' => ['emf'], + 'application/emma+xml' => ['emma'], + 'application/epub+zip' => ['epub'], + 'application/exi' => ['exi'], + 'application/font-tdpfr' => ['pfr'], + 'application/font-woff' => ['woff'], + 'application/futuresplash' => ['swf', 'spl'], + 'application/geo+json' => ['geojson', 'geo.json'], + 'application/gml+xml' => ['gml'], + 'application/gnunet-directory' => ['gnd'], + 'application/gpx' => ['gpx'], + 'application/gpx+xml' => ['gpx'], + 'application/gxf' => ['gxf'], + 'application/gzip' => ['gz'], + 'application/hyperstudio' => ['stk'], + 'application/ico' => ['ico'], + 'application/ics' => ['vcs', 'ics'], + 'application/illustrator' => ['ai'], + 'application/inkml+xml' => ['ink', 'inkml'], + 'application/ipfix' => ['ipfix'], + 'application/java' => ['class'], + 'application/java-archive' => ['jar'], + 'application/java-byte-code' => ['class'], + 'application/java-serialized-object' => ['ser'], + 'application/java-vm' => ['class'], + 'application/javascript' => ['js', 'jsm', 'mjs'], + 'application/jrd+json' => ['jrd'], + 'application/json' => ['json'], + 'application/json-patch+json' => ['json-patch'], + 'application/jsonml+json' => ['jsonml'], + 'application/ld+json' => ['jsonld'], + 'application/lost+xml' => ['lostxml'], + 'application/lotus123' => ['123', 'wk1', 'wk3', 'wk4', 'wks'], + 'application/m3u' => ['m3u', 'm3u8', 'vlc'], + 'application/mac-binhex40' => ['hqx'], + 'application/mac-compactpro' => ['cpt'], + 'application/mads+xml' => ['mads'], + 'application/marc' => ['mrc'], + 'application/marcxml+xml' => ['mrcx'], + 'application/mathematica' => ['ma', 'nb', 'mb'], + 'application/mathml+xml' => ['mathml', 'mml'], + 'application/mbox' => ['mbox'], + 'application/mdb' => ['mdb'], + 'application/mediaservercontrol+xml' => ['mscml'], + 'application/metalink+xml' => ['metalink'], + 'application/metalink4+xml' => ['meta4'], + 'application/mets+xml' => ['mets'], + 'application/mods+xml' => ['mods'], + 'application/mp21' => ['m21', 'mp21'], + 'application/mp4' => ['mp4s'], + 'application/ms-tnef' => ['tnef', 'tnf'], + 'application/msaccess' => ['mdb'], + 'application/msexcel' => ['xls', 'xlc', 'xll', 'xlm', 'xlw', 'xla', 'xlt', 'xld'], + 'application/mspowerpoint' => ['ppz', 'ppt', 'pps', 'pot'], + 'application/msword' => ['doc', 'dot'], + 'application/msword-template' => ['dot'], + 'application/mxf' => ['mxf'], + 'application/nappdf' => ['pdf'], + 'application/octet-stream' => ['bin', 'dms', 'lrf', 'mar', 'so', 'dist', 'distz', 'pkg', 'bpk', 'dump', 'elc', 'deploy'], + 'application/oda' => ['oda'], + 'application/oebps-package+xml' => ['opf'], + 'application/ogg' => ['ogx'], + 'application/omdoc+xml' => ['omdoc'], + 'application/onenote' => ['onetoc', 'onetoc2', 'onetmp', 'onepkg'], + 'application/owl+xml' => ['owx'], + 'application/oxps' => ['oxps', 'xps'], + 'application/patch-ops-error+xml' => ['xer'], + 'application/pcap' => ['pcap', 'cap', 'dmp'], + 'application/pdf' => ['pdf'], + 'application/pgp' => ['pgp', 'gpg', 'asc'], + 'application/pgp-encrypted' => ['pgp', 'gpg', 'asc'], + 'application/pgp-keys' => ['skr', 'pkr', 'asc', 'pgp', 'gpg'], + 'application/pgp-signature' => ['asc', 'sig', 'pgp', 'gpg'], + 'application/photoshop' => ['psd'], + 'application/pics-rules' => ['prf'], + 'application/pkcs10' => ['p10'], + 'application/pkcs12' => ['p12', 'pfx'], + 'application/pkcs7-mime' => ['p7m', 'p7c'], + 'application/pkcs7-signature' => ['p7s'], + 'application/pkcs8' => ['p8'], + 'application/pkcs8-encrypted' => ['p8e'], + 'application/pkix-attr-cert' => ['ac'], + 'application/pkix-cert' => ['cer'], + 'application/pkix-crl' => ['crl'], + 'application/pkix-pkipath' => ['pkipath'], + 'application/pkixcmp' => ['pki'], + 'application/pls' => ['pls'], + 'application/pls+xml' => ['pls'], + 'application/postscript' => ['ai', 'eps', 'ps'], + 'application/powerpoint' => ['ppz', 'ppt', 'pps', 'pot'], + 'application/prs.cww' => ['cww'], + 'application/pskc+xml' => ['pskcxml'], + 'application/ram' => ['ram'], + 'application/raml+yaml' => ['raml'], + 'application/rdf+xml' => ['rdf', 'rdfs', 'owl'], + 'application/reginfo+xml' => ['rif'], + 'application/relax-ng-compact-syntax' => ['rnc'], + 'application/resource-lists+xml' => ['rl'], + 'application/resource-lists-diff+xml' => ['rld'], + 'application/rls-services+xml' => ['rs'], + 'application/rpki-ghostbusters' => ['gbr'], + 'application/rpki-manifest' => ['mft'], + 'application/rpki-roa' => ['roa'], + 'application/rsd+xml' => ['rsd'], + 'application/rss+xml' => ['rss'], + 'application/rtf' => ['rtf'], + 'application/sbml+xml' => ['sbml'], + 'application/scvp-cv-request' => ['scq'], + 'application/scvp-cv-response' => ['scs'], + 'application/scvp-vp-request' => ['spq'], + 'application/scvp-vp-response' => ['spp'], + 'application/sdp' => ['sdp'], + 'application/set-payment-initiation' => ['setpay'], + 'application/set-registration-initiation' => ['setreg'], + 'application/shf+xml' => ['shf'], + 'application/sieve' => ['siv'], + 'application/smil' => ['smil', 'smi', 'sml', 'kino'], + 'application/smil+xml' => ['smi', 'smil', 'sml', 'kino'], + 'application/sparql-query' => ['rq'], + 'application/sparql-results+xml' => ['srx'], + 'application/sql' => ['sql'], + 'application/srgs' => ['gram'], + 'application/srgs+xml' => ['grxml'], + 'application/sru+xml' => ['sru'], + 'application/ssdl+xml' => ['ssdl'], + 'application/ssml+xml' => ['ssml'], + 'application/stuffit' => ['sit'], + 'application/tei+xml' => ['tei', 'teicorpus'], + 'application/thraud+xml' => ['tfi'], + 'application/timestamped-data' => ['tsd'], + 'application/trig' => ['trig'], + 'application/vnd.3gpp.pic-bw-large' => ['plb'], + 'application/vnd.3gpp.pic-bw-small' => ['psb'], + 'application/vnd.3gpp.pic-bw-var' => ['pvb'], + 'application/vnd.3gpp2.tcap' => ['tcap'], + 'application/vnd.3m.post-it-notes' => ['pwn'], + 'application/vnd.accpac.simply.aso' => ['aso'], + 'application/vnd.accpac.simply.imp' => ['imp'], + 'application/vnd.acucobol' => ['acu'], + 'application/vnd.acucorp' => ['atc', 'acutc'], + 'application/vnd.adobe.air-application-installer-package+zip' => ['air'], + 'application/vnd.adobe.flash.movie' => ['swf', 'spl'], + 'application/vnd.adobe.formscentral.fcdt' => ['fcdt'], + 'application/vnd.adobe.fxp' => ['fxp', 'fxpl'], + 'application/vnd.adobe.illustrator' => ['ai'], + 'application/vnd.adobe.xdp+xml' => ['xdp'], + 'application/vnd.adobe.xfdf' => ['xfdf'], + 'application/vnd.ahead.space' => ['ahead'], + 'application/vnd.airzip.filesecure.azf' => ['azf'], + 'application/vnd.airzip.filesecure.azs' => ['azs'], + 'application/vnd.amazon.ebook' => ['azw'], + 'application/vnd.americandynamics.acc' => ['acc'], + 'application/vnd.amiga.ami' => ['ami'], + 'application/vnd.android.package-archive' => ['apk'], + 'application/vnd.anser-web-certificate-issue-initiation' => ['cii'], + 'application/vnd.anser-web-funds-transfer-initiation' => ['fti'], + 'application/vnd.antix.game-component' => ['atx'], + 'application/vnd.appimage' => ['appimage'], + 'application/vnd.apple.installer+xml' => ['mpkg'], + 'application/vnd.apple.keynote' => ['key'], + 'application/vnd.apple.mpegurl' => ['m3u8', 'm3u'], + 'application/vnd.aristanetworks.swi' => ['swi'], + 'application/vnd.astraea-software.iota' => ['iota'], + 'application/vnd.audiograph' => ['aep'], + 'application/vnd.blueice.multipass' => ['mpm'], + 'application/vnd.bmi' => ['bmi'], + 'application/vnd.businessobjects' => ['rep'], + 'application/vnd.chemdraw+xml' => ['cdxml'], + 'application/vnd.chess-pgn' => ['pgn'], + 'application/vnd.chipnuts.karaoke-mmd' => ['mmd'], + 'application/vnd.cinderella' => ['cdy'], + 'application/vnd.claymore' => ['cla'], + 'application/vnd.cloanto.rp9' => ['rp9'], + 'application/vnd.clonk.c4group' => ['c4g', 'c4d', 'c4f', 'c4p', 'c4u'], + 'application/vnd.cluetrust.cartomobile-config' => ['c11amc'], + 'application/vnd.cluetrust.cartomobile-config-pkg' => ['c11amz'], + 'application/vnd.coffeescript' => ['coffee'], + 'application/vnd.comicbook+zip' => ['cbz'], + 'application/vnd.comicbook-rar' => ['cbr'], + 'application/vnd.commonspace' => ['csp'], + 'application/vnd.contact.cmsg' => ['cdbcmsg'], + 'application/vnd.corel-draw' => ['cdr'], + 'application/vnd.cosmocaller' => ['cmc'], + 'application/vnd.crick.clicker' => ['clkx'], + 'application/vnd.crick.clicker.keyboard' => ['clkk'], + 'application/vnd.crick.clicker.palette' => ['clkp'], + 'application/vnd.crick.clicker.template' => ['clkt'], + 'application/vnd.crick.clicker.wordbank' => ['clkw'], + 'application/vnd.criticaltools.wbs+xml' => ['wbs'], + 'application/vnd.ctc-posml' => ['pml'], + 'application/vnd.cups-ppd' => ['ppd'], + 'application/vnd.curl.car' => ['car'], + 'application/vnd.curl.pcurl' => ['pcurl'], + 'application/vnd.dart' => ['dart'], + 'application/vnd.data-vision.rdz' => ['rdz'], + 'application/vnd.debian.binary-package' => ['deb', 'udeb'], + 'application/vnd.dece.data' => ['uvf', 'uvvf', 'uvd', 'uvvd'], + 'application/vnd.dece.ttml+xml' => ['uvt', 'uvvt'], + 'application/vnd.dece.unspecified' => ['uvx', 'uvvx'], + 'application/vnd.dece.zip' => ['uvz', 'uvvz'], + 'application/vnd.denovo.fcselayout-link' => ['fe_launch'], + 'application/vnd.dna' => ['dna'], + 'application/vnd.dolby.mlp' => ['mlp'], + 'application/vnd.dpgraph' => ['dpg'], + 'application/vnd.dreamfactory' => ['dfac'], + 'application/vnd.ds-keypoint' => ['kpxx'], + 'application/vnd.dvb.ait' => ['ait'], + 'application/vnd.dvb.service' => ['svc'], + 'application/vnd.dynageo' => ['geo'], + 'application/vnd.ecowin.chart' => ['mag'], + 'application/vnd.emusic-emusic_package' => ['emp'], + 'application/vnd.enliven' => ['nml'], + 'application/vnd.epson.esf' => ['esf'], + 'application/vnd.epson.msf' => ['msf'], + 'application/vnd.epson.quickanime' => ['qam'], + 'application/vnd.epson.salt' => ['slt'], + 'application/vnd.epson.ssf' => ['ssf'], + 'application/vnd.eszigno3+xml' => ['es3', 'et3'], + 'application/vnd.ezpix-album' => ['ez2'], + 'application/vnd.ezpix-package' => ['ez3'], + 'application/vnd.fdf' => ['fdf'], + 'application/vnd.fdsn.mseed' => ['mseed'], + 'application/vnd.fdsn.seed' => ['seed', 'dataless'], + 'application/vnd.flatpak' => ['flatpak', 'xdgapp'], + 'application/vnd.flatpak.ref' => ['flatpakref'], + 'application/vnd.flatpak.repo' => ['flatpakrepo'], + 'application/vnd.flographit' => ['gph'], + 'application/vnd.fluxtime.clip' => ['ftc'], + 'application/vnd.framemaker' => ['fm', 'frame', 'maker', 'book'], + 'application/vnd.frogans.fnc' => ['fnc'], + 'application/vnd.frogans.ltf' => ['ltf'], + 'application/vnd.fsc.weblaunch' => ['fsc'], + 'application/vnd.fujitsu.oasys' => ['oas'], + 'application/vnd.fujitsu.oasys2' => ['oa2'], + 'application/vnd.fujitsu.oasys3' => ['oa3'], + 'application/vnd.fujitsu.oasysgp' => ['fg5'], + 'application/vnd.fujitsu.oasysprs' => ['bh2'], + 'application/vnd.fujixerox.ddd' => ['ddd'], + 'application/vnd.fujixerox.docuworks' => ['xdw'], + 'application/vnd.fujixerox.docuworks.binder' => ['xbd'], + 'application/vnd.fuzzysheet' => ['fzs'], + 'application/vnd.genomatix.tuxedo' => ['txd'], + 'application/vnd.geo+json' => ['geojson', 'geo.json'], + 'application/vnd.geogebra.file' => ['ggb'], + 'application/vnd.geogebra.tool' => ['ggt'], + 'application/vnd.geometry-explorer' => ['gex', 'gre'], + 'application/vnd.geonext' => ['gxt'], + 'application/vnd.geoplan' => ['g2w'], + 'application/vnd.geospace' => ['g3w'], + 'application/vnd.gmx' => ['gmx'], + 'application/vnd.google-earth.kml+xml' => ['kml'], + 'application/vnd.google-earth.kmz' => ['kmz'], + 'application/vnd.grafeq' => ['gqf', 'gqs'], + 'application/vnd.groove-account' => ['gac'], + 'application/vnd.groove-help' => ['ghf'], + 'application/vnd.groove-identity-message' => ['gim'], + 'application/vnd.groove-injector' => ['grv'], + 'application/vnd.groove-tool-message' => ['gtm'], + 'application/vnd.groove-tool-template' => ['tpl'], + 'application/vnd.groove-vcard' => ['vcg'], + 'application/vnd.haansoft-hwp' => ['hwp'], + 'application/vnd.haansoft-hwt' => ['hwt'], + 'application/vnd.hal+xml' => ['hal'], + 'application/vnd.handheld-entertainment+xml' => ['zmm'], + 'application/vnd.hbci' => ['hbci'], + 'application/vnd.hhe.lesson-player' => ['les'], + 'application/vnd.hp-hpgl' => ['hpgl'], + 'application/vnd.hp-hpid' => ['hpid'], + 'application/vnd.hp-hps' => ['hps'], + 'application/vnd.hp-jlyt' => ['jlt'], + 'application/vnd.hp-pcl' => ['pcl'], + 'application/vnd.hp-pclxl' => ['pclxl'], + 'application/vnd.hydrostatix.sof-data' => ['sfd-hdstx'], + 'application/vnd.ibm.minipay' => ['mpy'], + 'application/vnd.ibm.modcap' => ['afp', 'listafp', 'list3820'], + 'application/vnd.ibm.rights-management' => ['irm'], + 'application/vnd.ibm.secure-container' => ['sc'], + 'application/vnd.iccprofile' => ['icc', 'icm'], + 'application/vnd.igloader' => ['igl'], + 'application/vnd.immervision-ivp' => ['ivp'], + 'application/vnd.immervision-ivu' => ['ivu'], + 'application/vnd.insors.igm' => ['igm'], + 'application/vnd.intercon.formnet' => ['xpw', 'xpx'], + 'application/vnd.intergeo' => ['i2g'], + 'application/vnd.intu.qbo' => ['qbo'], + 'application/vnd.intu.qfx' => ['qfx'], + 'application/vnd.ipunplugged.rcprofile' => ['rcprofile'], + 'application/vnd.irepository.package+xml' => ['irp'], + 'application/vnd.is-xpr' => ['xpr'], + 'application/vnd.isac.fcs' => ['fcs'], + 'application/vnd.jam' => ['jam'], + 'application/vnd.jcp.javame.midlet-rms' => ['rms'], + 'application/vnd.jisp' => ['jisp'], + 'application/vnd.joost.joda-archive' => ['joda'], + 'application/vnd.kahootz' => ['ktz', 'ktr'], + 'application/vnd.kde.karbon' => ['karbon'], + 'application/vnd.kde.kchart' => ['chrt'], + 'application/vnd.kde.kformula' => ['kfo'], + 'application/vnd.kde.kivio' => ['flw'], + 'application/vnd.kde.kontour' => ['kon'], + 'application/vnd.kde.kpresenter' => ['kpr', 'kpt'], + 'application/vnd.kde.kspread' => ['ksp'], + 'application/vnd.kde.kword' => ['kwd', 'kwt'], + 'application/vnd.kenameaapp' => ['htke'], + 'application/vnd.kidspiration' => ['kia'], + 'application/vnd.kinar' => ['kne', 'knp'], + 'application/vnd.koan' => ['skp', 'skd', 'skt', 'skm'], + 'application/vnd.kodak-descriptor' => ['sse'], + 'application/vnd.las.las+xml' => ['lasxml'], + 'application/vnd.llamagraphics.life-balance.desktop' => ['lbd'], + 'application/vnd.llamagraphics.life-balance.exchange+xml' => ['lbe'], + 'application/vnd.lotus-1-2-3' => ['123', 'wk1', 'wk3', 'wk4', 'wks'], + 'application/vnd.lotus-approach' => ['apr'], + 'application/vnd.lotus-freelance' => ['pre'], + 'application/vnd.lotus-notes' => ['nsf'], + 'application/vnd.lotus-organizer' => ['org'], + 'application/vnd.lotus-screencam' => ['scm'], + 'application/vnd.lotus-wordpro' => ['lwp'], + 'application/vnd.macports.portpkg' => ['portpkg'], + 'application/vnd.mcd' => ['mcd'], + 'application/vnd.medcalcdata' => ['mc1'], + 'application/vnd.mediastation.cdkey' => ['cdkey'], + 'application/vnd.mfer' => ['mwf'], + 'application/vnd.mfmp' => ['mfm'], + 'application/vnd.micrografx.flo' => ['flo'], + 'application/vnd.micrografx.igx' => ['igx'], + 'application/vnd.mif' => ['mif'], + 'application/vnd.mobius.daf' => ['daf'], + 'application/vnd.mobius.dis' => ['dis'], + 'application/vnd.mobius.mbk' => ['mbk'], + 'application/vnd.mobius.mqy' => ['mqy'], + 'application/vnd.mobius.msl' => ['msl'], + 'application/vnd.mobius.plc' => ['plc'], + 'application/vnd.mobius.txf' => ['txf'], + 'application/vnd.mophun.application' => ['mpn'], + 'application/vnd.mophun.certificate' => ['mpc'], + 'application/vnd.mozilla.xul+xml' => ['xul'], + 'application/vnd.ms-access' => ['mdb'], + 'application/vnd.ms-artgalry' => ['cil'], + 'application/vnd.ms-asf' => ['asf'], + 'application/vnd.ms-cab-compressed' => ['cab'], + 'application/vnd.ms-excel' => ['xls', 'xlm', 'xla', 'xlc', 'xlt', 'xlw', 'xll', 'xld'], + 'application/vnd.ms-excel.addin.macroenabled.12' => ['xlam'], + 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => ['xlsb'], + 'application/vnd.ms-excel.sheet.macroenabled.12' => ['xlsm'], + 'application/vnd.ms-excel.template.macroenabled.12' => ['xltm'], + 'application/vnd.ms-fontobject' => ['eot'], + 'application/vnd.ms-htmlhelp' => ['chm'], + 'application/vnd.ms-ims' => ['ims'], + 'application/vnd.ms-lrm' => ['lrm'], + 'application/vnd.ms-officetheme' => ['thmx'], + 'application/vnd.ms-pki.seccat' => ['cat'], + 'application/vnd.ms-pki.stl' => ['stl'], + 'application/vnd.ms-powerpoint' => ['ppt', 'pps', 'pot', 'ppz'], + 'application/vnd.ms-powerpoint.addin.macroenabled.12' => ['ppam'], + 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => ['pptm'], + 'application/vnd.ms-powerpoint.slide.macroenabled.12' => ['sldm'], + 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => ['ppsm'], + 'application/vnd.ms-powerpoint.template.macroenabled.12' => ['potm'], + 'application/vnd.ms-project' => ['mpp', 'mpt'], + 'application/vnd.ms-publisher' => ['pub'], + 'application/vnd.ms-tnef' => ['tnef', 'tnf'], + 'application/vnd.ms-visio.drawing.macroenabled.main+xml' => ['vsdm'], + 'application/vnd.ms-visio.drawing.main+xml' => ['vsdx'], + 'application/vnd.ms-visio.stencil.macroenabled.main+xml' => ['vssm'], + 'application/vnd.ms-visio.stencil.main+xml' => ['vssx'], + 'application/vnd.ms-visio.template.macroenabled.main+xml' => ['vstm'], + 'application/vnd.ms-visio.template.main+xml' => ['vstx'], + 'application/vnd.ms-word' => ['doc'], + 'application/vnd.ms-word.document.macroenabled.12' => ['docm'], + 'application/vnd.ms-word.template.macroenabled.12' => ['dotm'], + 'application/vnd.ms-works' => ['wps', 'wks', 'wcm', 'wdb', 'xlr'], + 'application/vnd.ms-wpl' => ['wpl'], + 'application/vnd.ms-xpsdocument' => ['xps', 'oxps'], + 'application/vnd.msaccess' => ['mdb'], + 'application/vnd.mseq' => ['mseq'], + 'application/vnd.musician' => ['mus'], + 'application/vnd.muvee.style' => ['msty'], + 'application/vnd.mynfc' => ['taglet'], + 'application/vnd.neurolanguage.nlu' => ['nlu'], + 'application/vnd.nintendo.snes.rom' => ['sfc', 'smc'], + 'application/vnd.nitf' => ['ntf', 'nitf'], + 'application/vnd.noblenet-directory' => ['nnd'], + 'application/vnd.noblenet-sealer' => ['nns'], + 'application/vnd.noblenet-web' => ['nnw'], + 'application/vnd.nokia.n-gage.data' => ['ngdat'], + 'application/vnd.nokia.n-gage.symbian.install' => ['n-gage'], + 'application/vnd.nokia.radio-preset' => ['rpst'], + 'application/vnd.nokia.radio-presets' => ['rpss'], + 'application/vnd.novadigm.edm' => ['edm'], + 'application/vnd.novadigm.edx' => ['edx'], + 'application/vnd.novadigm.ext' => ['ext'], + 'application/vnd.oasis.docbook+xml' => ['dbk', 'docbook'], + 'application/vnd.oasis.opendocument.chart' => ['odc'], + 'application/vnd.oasis.opendocument.chart-template' => ['otc'], + 'application/vnd.oasis.opendocument.database' => ['odb'], + 'application/vnd.oasis.opendocument.formula' => ['odf'], + 'application/vnd.oasis.opendocument.formula-template' => ['odft', 'otf'], + 'application/vnd.oasis.opendocument.graphics' => ['odg'], + 'application/vnd.oasis.opendocument.graphics-flat-xml' => ['fodg'], + 'application/vnd.oasis.opendocument.graphics-template' => ['otg'], + 'application/vnd.oasis.opendocument.image' => ['odi'], + 'application/vnd.oasis.opendocument.image-template' => ['oti'], + 'application/vnd.oasis.opendocument.presentation' => ['odp'], + 'application/vnd.oasis.opendocument.presentation-flat-xml' => ['fodp'], + 'application/vnd.oasis.opendocument.presentation-template' => ['otp'], + 'application/vnd.oasis.opendocument.spreadsheet' => ['ods'], + 'application/vnd.oasis.opendocument.spreadsheet-flat-xml' => ['fods'], + 'application/vnd.oasis.opendocument.spreadsheet-template' => ['ots'], + 'application/vnd.oasis.opendocument.text' => ['odt'], + 'application/vnd.oasis.opendocument.text-flat-xml' => ['fodt'], + 'application/vnd.oasis.opendocument.text-master' => ['odm'], + 'application/vnd.oasis.opendocument.text-template' => ['ott'], + 'application/vnd.oasis.opendocument.text-web' => ['oth'], + 'application/vnd.olpc-sugar' => ['xo'], + 'application/vnd.oma.dd2+xml' => ['dd2'], + 'application/vnd.openofficeorg.extension' => ['oxt'], + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => ['pptx'], + 'application/vnd.openxmlformats-officedocument.presentationml.slide' => ['sldx'], + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => ['ppsx'], + 'application/vnd.openxmlformats-officedocument.presentationml.template' => ['potx'], + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['xlsx'], + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => ['xltx'], + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => ['docx'], + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => ['dotx'], + 'application/vnd.osgeo.mapguide.package' => ['mgp'], + 'application/vnd.osgi.dp' => ['dp'], + 'application/vnd.osgi.subsystem' => ['esa'], + 'application/vnd.palm' => ['pdb', 'pqa', 'oprc', 'prc'], + 'application/vnd.pawaafile' => ['paw'], + 'application/vnd.pg.format' => ['str'], + 'application/vnd.pg.osasli' => ['ei6'], + 'application/vnd.picsel' => ['efif'], + 'application/vnd.pmi.widget' => ['wg'], + 'application/vnd.pocketlearn' => ['plf'], + 'application/vnd.powerbuilder6' => ['pbd'], + 'application/vnd.previewsystems.box' => ['box'], + 'application/vnd.proteus.magazine' => ['mgz'], + 'application/vnd.publishare-delta-tree' => ['qps'], + 'application/vnd.pvi.ptid1' => ['ptid'], + 'application/vnd.quark.quarkxpress' => ['qxd', 'qxt', 'qwd', 'qwt', 'qxl', 'qxb'], + 'application/vnd.rar' => ['rar'], + 'application/vnd.realvnc.bed' => ['bed'], + 'application/vnd.recordare.musicxml' => ['mxl'], + 'application/vnd.recordare.musicxml+xml' => ['musicxml'], + 'application/vnd.rig.cryptonote' => ['cryptonote'], + 'application/vnd.rim.cod' => ['cod'], + 'application/vnd.rn-realmedia' => ['rm', 'rmj', 'rmm', 'rms', 'rmx', 'rmvb'], + 'application/vnd.rn-realmedia-vbr' => ['rmvb', 'rm', 'rmj', 'rmm', 'rms', 'rmx'], + 'application/vnd.route66.link66+xml' => ['link66'], + 'application/vnd.sailingtracker.track' => ['st'], + 'application/vnd.sdp' => ['sdp'], + 'application/vnd.seemail' => ['see'], + 'application/vnd.sema' => ['sema'], + 'application/vnd.semd' => ['semd'], + 'application/vnd.semf' => ['semf'], + 'application/vnd.shana.informed.formdata' => ['ifm'], + 'application/vnd.shana.informed.formtemplate' => ['itp'], + 'application/vnd.shana.informed.interchange' => ['iif'], + 'application/vnd.shana.informed.package' => ['ipk'], + 'application/vnd.simtech-mindmapper' => ['twd', 'twds'], + 'application/vnd.smaf' => ['mmf', 'smaf'], + 'application/vnd.smart.teacher' => ['teacher'], + 'application/vnd.snap' => ['snap'], + 'application/vnd.solent.sdkm+xml' => ['sdkm', 'sdkd'], + 'application/vnd.spotfire.dxp' => ['dxp'], + 'application/vnd.spotfire.sfs' => ['sfs'], + 'application/vnd.sqlite3' => ['sqlite3'], + 'application/vnd.squashfs' => ['sqsh'], + 'application/vnd.stardivision.calc' => ['sdc'], + 'application/vnd.stardivision.chart' => ['sds'], + 'application/vnd.stardivision.draw' => ['sda'], + 'application/vnd.stardivision.impress' => ['sdd', 'sdp'], + 'application/vnd.stardivision.mail' => ['smd'], + 'application/vnd.stardivision.math' => ['smf'], + 'application/vnd.stardivision.writer' => ['sdw', 'vor', 'sgl'], + 'application/vnd.stardivision.writer-global' => ['sgl', 'sdw', 'vor'], + 'application/vnd.stepmania.package' => ['smzip'], + 'application/vnd.stepmania.stepchart' => ['sm'], + 'application/vnd.sun.xml.base' => ['odb'], + 'application/vnd.sun.xml.calc' => ['sxc'], + 'application/vnd.sun.xml.calc.template' => ['stc'], + 'application/vnd.sun.xml.draw' => ['sxd'], + 'application/vnd.sun.xml.draw.template' => ['std'], + 'application/vnd.sun.xml.impress' => ['sxi'], + 'application/vnd.sun.xml.impress.template' => ['sti'], + 'application/vnd.sun.xml.math' => ['sxm'], + 'application/vnd.sun.xml.writer' => ['sxw'], + 'application/vnd.sun.xml.writer.global' => ['sxg'], + 'application/vnd.sun.xml.writer.template' => ['stw'], + 'application/vnd.sus-calendar' => ['sus', 'susp'], + 'application/vnd.svd' => ['svd'], + 'application/vnd.symbian.install' => ['sis', 'sisx'], + 'application/vnd.syncml+xml' => ['xsm'], + 'application/vnd.syncml.dm+wbxml' => ['bdm'], + 'application/vnd.syncml.dm+xml' => ['xdm'], + 'application/vnd.tao.intent-module-archive' => ['tao'], + 'application/vnd.tcpdump.pcap' => ['pcap', 'cap', 'dmp'], + 'application/vnd.tmobile-livetv' => ['tmo'], + 'application/vnd.trid.tpt' => ['tpt'], + 'application/vnd.triscape.mxs' => ['mxs'], + 'application/vnd.trueapp' => ['tra'], + 'application/vnd.ufdl' => ['ufd', 'ufdl'], + 'application/vnd.uiq.theme' => ['utz'], + 'application/vnd.umajin' => ['umj'], + 'application/vnd.unity' => ['unityweb'], + 'application/vnd.uoml+xml' => ['uoml'], + 'application/vnd.vcx' => ['vcx'], + 'application/vnd.visio' => ['vsd', 'vst', 'vss', 'vsw'], + 'application/vnd.visionary' => ['vis'], + 'application/vnd.vsf' => ['vsf'], + 'application/vnd.wap.wbxml' => ['wbxml'], + 'application/vnd.wap.wmlc' => ['wmlc'], + 'application/vnd.wap.wmlscriptc' => ['wmlsc'], + 'application/vnd.webturbo' => ['wtb'], + 'application/vnd.wolfram.player' => ['nbp'], + 'application/vnd.wordperfect' => ['wpd', 'wp', 'wp4', 'wp5', 'wp6', 'wpp'], + 'application/vnd.wqd' => ['wqd'], + 'application/vnd.wt.stf' => ['stf'], + 'application/vnd.xara' => ['xar'], + 'application/vnd.xdgapp' => ['flatpak', 'xdgapp'], + 'application/vnd.xfdl' => ['xfdl'], + 'application/vnd.yamaha.hv-dic' => ['hvd'], + 'application/vnd.yamaha.hv-script' => ['hvs'], + 'application/vnd.yamaha.hv-voice' => ['hvp'], + 'application/vnd.yamaha.openscoreformat' => ['osf'], + 'application/vnd.yamaha.openscoreformat.osfpvg+xml' => ['osfpvg'], + 'application/vnd.yamaha.smaf-audio' => ['saf'], + 'application/vnd.yamaha.smaf-phrase' => ['spf'], + 'application/vnd.yellowriver-custom-menu' => ['cmp'], + 'application/vnd.youtube.yt' => ['yt'], + 'application/vnd.zul' => ['zir', 'zirz'], + 'application/vnd.zzazz.deck+xml' => ['zaz'], + 'application/voicexml+xml' => ['vxml'], + 'application/widget' => ['wgt'], + 'application/winhlp' => ['hlp'], + 'application/wk1' => ['123', 'wk1', 'wk3', 'wk4', 'wks'], + 'application/wmf' => ['wmf'], + 'application/wordperfect' => ['wp', 'wp4', 'wp5', 'wp6', 'wpd', 'wpp'], + 'application/wsdl+xml' => ['wsdl'], + 'application/wspolicy+xml' => ['wspolicy'], + 'application/wwf' => ['wwf'], + 'application/x-123' => ['123', 'wk1', 'wk3', 'wk4', 'wks'], + 'application/x-7z-compressed' => ['7z'], + 'application/x-abiword' => ['abw', 'abw.CRASHED', 'abw.gz', 'zabw'], + 'application/x-ace' => ['ace'], + 'application/x-ace-compressed' => ['ace'], + 'application/x-alz' => ['alz'], + 'application/x-amiga-disk-format' => ['adf'], + 'application/x-amipro' => ['sam'], + 'application/x-annodex' => ['anx'], + 'application/x-aportisdoc' => ['pdb', 'pdc'], + 'application/x-apple-diskimage' => ['dmg'], + 'application/x-applix-spreadsheet' => ['as'], + 'application/x-applix-word' => ['aw'], + 'application/x-archive' => ['a', 'ar'], + 'application/x-arj' => ['arj'], + 'application/x-asp' => ['asp'], + 'application/x-atari-2600-rom' => ['a26'], + 'application/x-atari-7800-rom' => ['a78'], + 'application/x-atari-lynx-rom' => ['lnx'], + 'application/x-authorware-bin' => ['aab', 'x32', 'u32', 'vox'], + 'application/x-authorware-map' => ['aam'], + 'application/x-authorware-seg' => ['aas'], + 'application/x-awk' => ['awk'], + 'application/x-bcpio' => ['bcpio'], + 'application/x-bittorrent' => ['torrent'], + 'application/x-blender' => ['blender', 'blend', 'BLEND'], + 'application/x-blorb' => ['blb', 'blorb'], + 'application/x-bsdiff' => ['bsdiff'], + 'application/x-bzdvi' => ['dvi.bz2'], + 'application/x-bzip' => ['bz', 'bz2'], + 'application/x-bzip-compressed-tar' => ['tar.bz2', 'tar.bz', 'tbz2', 'tbz', 'tb2'], + 'application/x-bzip2' => ['bz2', 'boz', 'bz'], + 'application/x-bzpdf' => ['pdf.bz2'], + 'application/x-bzpostscript' => ['ps.bz2'], + 'application/x-cb7' => ['cb7'], + 'application/x-cbr' => ['cbr', 'cba', 'cbt', 'cbz', 'cb7'], + 'application/x-cbt' => ['cbt'], + 'application/x-cbz' => ['cbz'], + 'application/x-ccmx' => ['ccmx'], + 'application/x-cd-image' => ['iso', 'iso9660'], + 'application/x-cdlink' => ['vcd'], + 'application/x-cdr' => ['cdr'], + 'application/x-cdrdao-toc' => ['toc'], + 'application/x-cfs-compressed' => ['cfs'], + 'application/x-chat' => ['chat'], + 'application/x-chess-pgn' => ['pgn'], + 'application/x-chm' => ['chm'], + 'application/x-cisco-vpn-settings' => ['pcf'], + 'application/x-compress' => ['Z'], + 'application/x-compressed-tar' => ['tar.gz', 'tgz'], + 'application/x-conference' => ['nsc'], + 'application/x-coreldraw' => ['cdr'], + 'application/x-cpio' => ['cpio'], + 'application/x-cpio-compressed' => ['cpio.gz'], + 'application/x-csh' => ['csh'], + 'application/x-cue' => ['cue'], + 'application/x-dar' => ['dar'], + 'application/x-dbase' => ['dbf'], + 'application/x-dbf' => ['dbf'], + 'application/x-dc-rom' => ['dc'], + 'application/x-deb' => ['deb', 'udeb'], + 'application/x-debian-package' => ['deb', 'udeb'], + 'application/x-designer' => ['ui'], + 'application/x-desktop' => ['desktop', 'kdelnk'], + 'application/x-dgc-compressed' => ['dgc'], + 'application/x-dia-diagram' => ['dia'], + 'application/x-dia-shape' => ['shape'], + 'application/x-director' => ['dir', 'dcr', 'dxr', 'cst', 'cct', 'cxt', 'w3d', 'fgd', 'swa'], + 'application/x-docbook+xml' => ['dbk', 'docbook'], + 'application/x-doom' => ['wad'], + 'application/x-doom-wad' => ['wad'], + 'application/x-dtbncx+xml' => ['ncx'], + 'application/x-dtbook+xml' => ['dtb'], + 'application/x-dtbresource+xml' => ['res'], + 'application/x-dvi' => ['dvi'], + 'application/x-e-theme' => ['etheme'], + 'application/x-egon' => ['egon'], + 'application/x-emf' => ['emf'], + 'application/x-envoy' => ['evy'], + 'application/x-eva' => ['eva'], + 'application/x-fd-file' => ['fd', 'qd'], + 'application/x-fds-disk' => ['fds'], + 'application/x-fictionbook' => ['fb2'], + 'application/x-fictionbook+xml' => ['fb2'], + 'application/x-flash-video' => ['flv'], + 'application/x-fluid' => ['fl'], + 'application/x-font-afm' => ['afm'], + 'application/x-font-bdf' => ['bdf'], + 'application/x-font-ghostscript' => ['gsf'], + 'application/x-font-linux-psf' => ['psf'], + 'application/x-font-otf' => ['otf'], + 'application/x-font-pcf' => ['pcf', 'pcf.Z', 'pcf.gz'], + 'application/x-font-snf' => ['snf'], + 'application/x-font-speedo' => ['spd'], + 'application/x-font-ttf' => ['ttf'], + 'application/x-font-ttx' => ['ttx'], + 'application/x-font-type1' => ['pfa', 'pfb', 'pfm', 'afm', 'gsf'], + 'application/x-font-woff' => ['woff'], + 'application/x-frame' => ['fm'], + 'application/x-freearc' => ['arc'], + 'application/x-futuresplash' => ['spl'], + 'application/x-gameboy-color-rom' => ['gbc', 'cgb'], + 'application/x-gameboy-rom' => ['gb', 'sgb'], + 'application/x-gamecube-iso-image' => ['iso'], + 'application/x-gamecube-rom' => ['iso'], + 'application/x-gamegear-rom' => ['gg'], + 'application/x-gba-rom' => ['gba', 'agb'], + 'application/x-gca-compressed' => ['gca'], + 'application/x-gedcom' => ['ged', 'gedcom'], + 'application/x-genesis-32x-rom' => ['32x', 'mdx'], + 'application/x-genesis-rom' => ['gen', 'smd'], + 'application/x-gettext' => ['po'], + 'application/x-gettext-translation' => ['gmo', 'mo'], + 'application/x-glade' => ['glade'], + 'application/x-glulx' => ['ulx'], + 'application/x-gnome-app-info' => ['desktop', 'kdelnk'], + 'application/x-gnucash' => ['gnucash', 'gnc', 'xac'], + 'application/x-gnumeric' => ['gnumeric'], + 'application/x-gnuplot' => ['gp', 'gplt', 'gnuplot'], + 'application/x-go-sgf' => ['sgf'], + 'application/x-gpx' => ['gpx'], + 'application/x-gpx+xml' => ['gpx'], + 'application/x-gramps-xml' => ['gramps'], + 'application/x-graphite' => ['gra'], + 'application/x-gtar' => ['gtar', 'tar', 'gem'], + 'application/x-gtk-builder' => ['ui'], + 'application/x-gz-font-linux-psf' => ['psf.gz'], + 'application/x-gzdvi' => ['dvi.gz'], + 'application/x-gzip' => ['gz'], + 'application/x-gzpdf' => ['pdf.gz'], + 'application/x-gzpostscript' => ['ps.gz'], + 'application/x-hdf' => ['hdf', 'hdf4', 'h4', 'hdf5', 'h5'], + 'application/x-hfe-file' => ['hfe'], + 'application/x-hfe-floppy-image' => ['hfe'], + 'application/x-hwp' => ['hwp'], + 'application/x-hwt' => ['hwt'], + 'application/x-ica' => ['ica'], + 'application/x-install-instructions' => ['install'], + 'application/x-ipynb+json' => ['ipynb'], + 'application/x-iso9660-appimage' => ['appimage'], + 'application/x-iso9660-image' => ['iso', 'iso9660'], + 'application/x-it87' => ['it87'], + 'application/x-iwork-keynote-sffkey' => ['key'], + 'application/x-jar' => ['jar'], + 'application/x-java' => ['class'], + 'application/x-java-archive' => ['jar'], + 'application/x-java-class' => ['class'], + 'application/x-java-jce-keystore' => ['jceks'], + 'application/x-java-jnlp-file' => ['jnlp'], + 'application/x-java-keystore' => ['jks', 'ks'], + 'application/x-java-pack200' => ['pack'], + 'application/x-java-vm' => ['class'], + 'application/x-javascript' => ['js', 'jsm', 'mjs'], + 'application/x-jbuilder-project' => ['jpr', 'jpx'], + 'application/x-karbon' => ['karbon'], + 'application/x-kchart' => ['chrt'], + 'application/x-kexi-connectiondata' => ['kexic'], + 'application/x-kexiproject-shortcut' => ['kexis'], + 'application/x-kexiproject-sqlite' => ['kexi'], + 'application/x-kexiproject-sqlite2' => ['kexi'], + 'application/x-kexiproject-sqlite3' => ['kexi'], + 'application/x-kformula' => ['kfo'], + 'application/x-killustrator' => ['kil'], + 'application/x-kivio' => ['flw'], + 'application/x-kontour' => ['kon'], + 'application/x-kpovmodeler' => ['kpm'], + 'application/x-kpresenter' => ['kpr', 'kpt'], + 'application/x-krita' => ['kra'], + 'application/x-kspread' => ['ksp'], + 'application/x-kugar' => ['kud'], + 'application/x-kword' => ['kwd', 'kwt'], + 'application/x-latex' => ['latex'], + 'application/x-lha' => ['lha', 'lzh'], + 'application/x-lhz' => ['lhz'], + 'application/x-linguist' => ['ts'], + 'application/x-lotus123' => ['123', 'wk1', 'wk3', 'wk4', 'wks'], + 'application/x-lrzip' => ['lrz'], + 'application/x-lrzip-compressed-tar' => ['tar.lrz', 'tlrz'], + 'application/x-lyx' => ['lyx'], + 'application/x-lz4' => ['lz4'], + 'application/x-lz4-compressed-tar' => ['tar.lz4'], + 'application/x-lzh-compressed' => ['lzh', 'lha'], + 'application/x-lzip' => ['lz'], + 'application/x-lzip-compressed-tar' => ['tar.lz'], + 'application/x-lzma' => ['lzma'], + 'application/x-lzma-compressed-tar' => ['tar.lzma', 'tlz'], + 'application/x-lzop' => ['lzo'], + 'application/x-lzpdf' => ['pdf.lz'], + 'application/x-m4' => ['m4'], + 'application/x-magicpoint' => ['mgp'], + 'application/x-markaby' => ['mab'], + 'application/x-mathematica' => ['nb'], + 'application/x-mdb' => ['mdb'], + 'application/x-mie' => ['mie'], + 'application/x-mif' => ['mif'], + 'application/x-mimearchive' => ['mhtml', 'mht'], + 'application/x-mobipocket-ebook' => ['prc', 'mobi'], + 'application/x-ms-application' => ['application'], + 'application/x-ms-asx' => ['asx', 'wax', 'wvx', 'wmx'], + 'application/x-ms-dos-executable' => ['exe'], + 'application/x-ms-shortcut' => ['lnk'], + 'application/x-ms-wim' => ['wim', 'swm'], + 'application/x-ms-wmd' => ['wmd'], + 'application/x-ms-wmz' => ['wmz'], + 'application/x-ms-xbap' => ['xbap'], + 'application/x-msaccess' => ['mdb'], + 'application/x-msbinder' => ['obd'], + 'application/x-mscardfile' => ['crd'], + 'application/x-msclip' => ['clp'], + 'application/x-msdownload' => ['exe', 'dll', 'com', 'bat', 'msi'], + 'application/x-msexcel' => ['xls', 'xlc', 'xll', 'xlm', 'xlw', 'xla', 'xlt', 'xld'], + 'application/x-msi' => ['msi'], + 'application/x-msmediaview' => ['mvb', 'm13', 'm14'], + 'application/x-msmetafile' => ['wmf', 'wmz', 'emf', 'emz'], + 'application/x-msmoney' => ['mny'], + 'application/x-mspowerpoint' => ['ppz', 'ppt', 'pps', 'pot'], + 'application/x-mspublisher' => ['pub'], + 'application/x-msschedule' => ['scd'], + 'application/x-msterminal' => ['trm'], + 'application/x-mswinurl' => ['url'], + 'application/x-msword' => ['doc'], + 'application/x-mswrite' => ['wri'], + 'application/x-msx-rom' => ['msx'], + 'application/x-n64-rom' => ['n64', 'z64', 'v64'], + 'application/x-navi-animation' => ['ani'], + 'application/x-neo-geo-pocket-color-rom' => ['ngc'], + 'application/x-neo-geo-pocket-rom' => ['ngp'], + 'application/x-nes-rom' => ['nes', 'nez', 'unf', 'unif'], + 'application/x-netcdf' => ['nc', 'cdf'], + 'application/x-netshow-channel' => ['nsc'], + 'application/x-nintendo-ds-rom' => ['nds'], + 'application/x-nzb' => ['nzb'], + 'application/x-object' => ['o'], + 'application/x-ogg' => ['ogx'], + 'application/x-oleo' => ['oleo'], + 'application/x-pagemaker' => ['p65', 'pm', 'pm6', 'pmd'], + 'application/x-pak' => ['pak'], + 'application/x-palm-database' => ['prc', 'pdb', 'pqa', 'oprc'], + 'application/x-par2' => ['PAR2', 'par2'], + 'application/x-partial-download' => ['wkdownload', 'crdownload', 'part'], + 'application/x-pc-engine-rom' => ['pce'], + 'application/x-pcap' => ['pcap', 'cap', 'dmp'], + 'application/x-pdf' => ['pdf'], + 'application/x-perl' => ['pl', 'PL', 'pm', 'al', 'perl', 'pod', 't'], + 'application/x-photoshop' => ['psd'], + 'application/x-php' => ['php', 'php3', 'php4', 'php5', 'phps'], + 'application/x-pkcs12' => ['p12', 'pfx'], + 'application/x-pkcs7-certificates' => ['p7b', 'spc'], + 'application/x-pkcs7-certreqresp' => ['p7r'], + 'application/x-planperfect' => ['pln'], + 'application/x-pocket-word' => ['psw'], + 'application/x-pw' => ['pw'], + 'application/x-python-bytecode' => ['pyc', 'pyo'], + 'application/x-qpress' => ['qp'], + 'application/x-qtiplot' => ['qti', 'qti.gz'], + 'application/x-quattropro' => ['wb1', 'wb2', 'wb3'], + 'application/x-quicktime-media-link' => ['qtl'], + 'application/x-quicktimeplayer' => ['qtl'], + 'application/x-qw' => ['qif'], + 'application/x-rar' => ['rar'], + 'application/x-rar-compressed' => ['rar'], + 'application/x-raw-disk-image' => ['raw-disk-image', 'img'], + 'application/x-raw-disk-image-xz-compressed' => ['raw-disk-image.xz', 'img.xz'], + 'application/x-raw-floppy-disk-image' => ['fd', 'qd'], + 'application/x-redhat-package-manager' => ['rpm'], + 'application/x-reject' => ['rej'], + 'application/x-research-info-systems' => ['ris'], + 'application/x-rnc' => ['rnc'], + 'application/x-rpm' => ['rpm'], + 'application/x-ruby' => ['rb'], + 'application/x-sami' => ['smi', 'sami'], + 'application/x-sap-file' => ['sap'], + 'application/x-saturn-rom' => ['bin', 'iso'], + 'application/x-sdp' => ['sdp'], + 'application/x-sega-cd-rom' => ['bin', 'iso'], + 'application/x-sg1000-rom' => ['sg'], + 'application/x-sh' => ['sh'], + 'application/x-shar' => ['shar'], + 'application/x-shared-library-la' => ['la'], + 'application/x-sharedlib' => ['so'], + 'application/x-shellscript' => ['sh'], + 'application/x-shockwave-flash' => ['swf', 'spl'], + 'application/x-shorten' => ['shn'], + 'application/x-siag' => ['siag'], + 'application/x-silverlight-app' => ['xap'], + 'application/x-sit' => ['sit'], + 'application/x-smaf' => ['mmf', 'smaf'], + 'application/x-sms-rom' => ['sms'], + 'application/x-snes-rom' => ['sfc', 'smc'], + 'application/x-source-rpm' => ['src.rpm', 'spm'], + 'application/x-spss-por' => ['por'], + 'application/x-spss-sav' => ['sav', 'zsav'], + 'application/x-spss-savefile' => ['sav', 'zsav'], + 'application/x-sql' => ['sql'], + 'application/x-sqlite2' => ['sqlite2'], + 'application/x-sqlite3' => ['sqlite3'], + 'application/x-srt' => ['srt'], + 'application/x-stuffit' => ['sit'], + 'application/x-stuffitx' => ['sitx'], + 'application/x-subrip' => ['srt'], + 'application/x-sv4cpio' => ['sv4cpio'], + 'application/x-sv4crc' => ['sv4crc'], + 'application/x-t3vm-image' => ['t3'], + 'application/x-t602' => ['602'], + 'application/x-tads' => ['gam'], + 'application/x-tar' => ['tar', 'gtar', 'gem'], + 'application/x-tarz' => ['tar.Z', 'taz'], + 'application/x-tcl' => ['tcl'], + 'application/x-tex' => ['tex', 'ltx', 'sty', 'cls', 'dtx', 'ins', 'latex'], + 'application/x-tex-gf' => ['gf'], + 'application/x-tex-pk' => ['pk'], + 'application/x-tex-tfm' => ['tfm'], + 'application/x-texinfo' => ['texinfo', 'texi'], + 'application/x-tgif' => ['obj'], + 'application/x-theme' => ['theme'], + 'application/x-thomson-cartridge-memo7' => ['m7'], + 'application/x-thomson-cassette' => ['k7'], + 'application/x-thomson-sap-image' => ['sap'], + 'application/x-trash' => ['bak', 'old', 'sik'], + 'application/x-trig' => ['trig'], + 'application/x-troff' => ['tr', 'roff', 't'], + 'application/x-troff-man' => ['man'], + 'application/x-tzo' => ['tar.lzo', 'tzo'], + 'application/x-ufraw' => ['ufraw'], + 'application/x-ustar' => ['ustar'], + 'application/x-virtual-boy-rom' => ['vb'], + 'application/x-vnd.kde.kexi' => ['kexi'], + 'application/x-wais-source' => ['src'], + 'application/x-wbfs' => ['iso'], + 'application/x-wia' => ['iso'], + 'application/x-wii-iso-image' => ['iso'], + 'application/x-wii-rom' => ['iso'], + 'application/x-wii-wad' => ['wad'], + 'application/x-windows-themepack' => ['themepack'], + 'application/x-wmf' => ['wmf'], + 'application/x-wonderswan-color-rom' => ['wsc'], + 'application/x-wonderswan-rom' => ['ws'], + 'application/x-wordperfect' => ['wp', 'wp4', 'wp5', 'wp6', 'wpd', 'wpp'], + 'application/x-wpg' => ['wpg'], + 'application/x-wwf' => ['wwf'], + 'application/x-x509-ca-cert' => ['der', 'crt', 'cert', 'pem'], + 'application/x-xar' => ['xar', 'pkg'], + 'application/x-xbel' => ['xbel'], + 'application/x-xfig' => ['fig'], + 'application/x-xliff' => ['xlf', 'xliff'], + 'application/x-xliff+xml' => ['xlf'], + 'application/x-xpinstall' => ['xpi'], + 'application/x-xspf+xml' => ['xspf'], + 'application/x-xz' => ['xz'], + 'application/x-xz-compressed-tar' => ['tar.xz', 'txz'], + 'application/x-xzpdf' => ['pdf.xz'], + 'application/x-yaml' => ['yaml', 'yml'], + 'application/x-zip' => ['zip'], + 'application/x-zip-compressed' => ['zip'], + 'application/x-zip-compressed-fb2' => ['fb2.zip'], + 'application/x-zmachine' => ['z1', 'z2', 'z3', 'z4', 'z5', 'z6', 'z7', 'z8'], + 'application/x-zoo' => ['zoo'], + 'application/xaml+xml' => ['xaml'], + 'application/xcap-diff+xml' => ['xdf'], + 'application/xenc+xml' => ['xenc'], + 'application/xhtml+xml' => ['xhtml', 'xht'], + 'application/xliff+xml' => ['xlf', 'xliff'], + 'application/xml' => ['xml', 'xsl', 'xbl', 'xsd', 'rng'], + 'application/xml-dtd' => ['dtd'], + 'application/xml-external-parsed-entity' => ['ent'], + 'application/xop+xml' => ['xop'], + 'application/xproc+xml' => ['xpl'], + 'application/xps' => ['oxps', 'xps'], + 'application/xslt+xml' => ['xslt', 'xsl'], + 'application/xspf+xml' => ['xspf'], + 'application/xv+xml' => ['mxml', 'xhvml', 'xvml', 'xvm'], + 'application/yang' => ['yang'], + 'application/yin+xml' => ['yin'], + 'application/zip' => ['zip'], + 'application/zlib' => ['zz'], + 'audio/3gpp' => ['3gp', '3gpp', '3ga'], + 'audio/3gpp-encrypted' => ['3gp', '3gpp', '3ga'], + 'audio/3gpp2' => ['3g2', '3gp2', '3gpp2'], + 'audio/aac' => ['aac', 'adts', 'ass'], + 'audio/ac3' => ['ac3'], + 'audio/adpcm' => ['adp'], + 'audio/amr' => ['amr'], + 'audio/amr-encrypted' => ['amr'], + 'audio/amr-wb' => ['awb'], + 'audio/amr-wb-encrypted' => ['awb'], + 'audio/annodex' => ['axa'], + 'audio/basic' => ['au', 'snd'], + 'audio/flac' => ['flac'], + 'audio/imelody' => ['imy', 'ime'], + 'audio/m3u' => ['m3u', 'm3u8', 'vlc'], + 'audio/m4a' => ['m4a', 'f4a'], + 'audio/midi' => ['mid', 'midi', 'kar', 'rmi'], + 'audio/mobile-xmf' => ['xmf'], + 'audio/mp2' => ['mp2'], + 'audio/mp3' => ['mp3', 'mpga'], + 'audio/mp4' => ['m4a', 'mp4a', 'f4a'], + 'audio/mpeg' => ['mpga', 'mp2', 'mp2a', 'mp3', 'm2a', 'm3a'], + 'audio/mpegurl' => ['m3u', 'm3u8', 'vlc'], + 'audio/ogg' => ['oga', 'ogg', 'spx', 'opus'], + 'audio/prs.sid' => ['sid', 'psid'], + 'audio/s3m' => ['s3m'], + 'audio/scpls' => ['pls'], + 'audio/silk' => ['sil'], + 'audio/tta' => ['tta'], + 'audio/usac' => ['loas', 'xhe'], + 'audio/vnd.audible' => ['aa', 'aax'], + 'audio/vnd.audible.aax' => ['aa', 'aax'], + 'audio/vnd.dece.audio' => ['uva', 'uvva'], + 'audio/vnd.digital-winds' => ['eol'], + 'audio/vnd.dra' => ['dra'], + 'audio/vnd.dts' => ['dts'], + 'audio/vnd.dts.hd' => ['dtshd'], + 'audio/vnd.lucent.voice' => ['lvp'], + 'audio/vnd.m-realaudio' => ['ra', 'rax'], + 'audio/vnd.ms-playready.media.pya' => ['pya'], + 'audio/vnd.nuera.ecelp4800' => ['ecelp4800'], + 'audio/vnd.nuera.ecelp7470' => ['ecelp7470'], + 'audio/vnd.nuera.ecelp9600' => ['ecelp9600'], + 'audio/vnd.rip' => ['rip'], + 'audio/vnd.rn-realaudio' => ['ra', 'rax'], + 'audio/vnd.wave' => ['wav'], + 'audio/vorbis' => ['oga', 'ogg'], + 'audio/wav' => ['wav'], + 'audio/webm' => ['weba'], + 'audio/wma' => ['wma'], + 'audio/x-aac' => ['aac', 'adts', 'ass'], + 'audio/x-aifc' => ['aifc', 'aiffc'], + 'audio/x-aiff' => ['aif', 'aiff', 'aifc'], + 'audio/x-aiffc' => ['aifc', 'aiffc'], + 'audio/x-amzxml' => ['amz'], + 'audio/x-annodex' => ['axa'], + 'audio/x-ape' => ['ape'], + 'audio/x-caf' => ['caf'], + 'audio/x-dts' => ['dts'], + 'audio/x-dtshd' => ['dtshd'], + 'audio/x-flac' => ['flac'], + 'audio/x-flac+ogg' => ['oga', 'ogg'], + 'audio/x-gsm' => ['gsm'], + 'audio/x-hx-aac-adts' => ['aac', 'adts', 'ass'], + 'audio/x-imelody' => ['imy', 'ime'], + 'audio/x-iriver-pla' => ['pla'], + 'audio/x-it' => ['it'], + 'audio/x-m3u' => ['m3u', 'm3u8', 'vlc'], + 'audio/x-m4a' => ['m4a', 'f4a'], + 'audio/x-m4b' => ['m4b', 'f4b'], + 'audio/x-m4r' => ['m4r'], + 'audio/x-matroska' => ['mka'], + 'audio/x-midi' => ['mid', 'midi', 'kar'], + 'audio/x-minipsf' => ['minipsf'], + 'audio/x-mo3' => ['mo3'], + 'audio/x-mod' => ['mod', 'ult', 'uni', 'm15', 'mtm', '669', 'med'], + 'audio/x-mp2' => ['mp2'], + 'audio/x-mp3' => ['mp3', 'mpga'], + 'audio/x-mp3-playlist' => ['m3u', 'm3u8', 'vlc'], + 'audio/x-mpeg' => ['mp3', 'mpga'], + 'audio/x-mpegurl' => ['m3u', 'm3u8', 'vlc'], + 'audio/x-mpg' => ['mp3', 'mpga'], + 'audio/x-ms-asx' => ['asx', 'wax', 'wvx', 'wmx'], + 'audio/x-ms-wax' => ['wax'], + 'audio/x-ms-wma' => ['wma'], + 'audio/x-musepack' => ['mpc', 'mpp', 'mp+'], + 'audio/x-ogg' => ['oga', 'ogg', 'opus'], + 'audio/x-oggflac' => ['oga', 'ogg'], + 'audio/x-opus+ogg' => ['opus'], + 'audio/x-pn-audibleaudio' => ['aa', 'aax'], + 'audio/x-pn-realaudio' => ['ram', 'ra', 'rax'], + 'audio/x-pn-realaudio-plugin' => ['rmp'], + 'audio/x-psf' => ['psf'], + 'audio/x-psflib' => ['psflib'], + 'audio/x-rn-3gpp-amr' => ['3gp', '3gpp', '3ga'], + 'audio/x-rn-3gpp-amr-encrypted' => ['3gp', '3gpp', '3ga'], + 'audio/x-rn-3gpp-amr-wb' => ['3gp', '3gpp', '3ga'], + 'audio/x-rn-3gpp-amr-wb-encrypted' => ['3gp', '3gpp', '3ga'], + 'audio/x-s3m' => ['s3m'], + 'audio/x-scpls' => ['pls'], + 'audio/x-shorten' => ['shn'], + 'audio/x-speex' => ['spx'], + 'audio/x-speex+ogg' => ['oga', 'ogg'], + 'audio/x-stm' => ['stm'], + 'audio/x-tta' => ['tta'], + 'audio/x-voc' => ['voc'], + 'audio/x-vorbis' => ['oga', 'ogg'], + 'audio/x-vorbis+ogg' => ['oga', 'ogg'], + 'audio/x-wav' => ['wav'], + 'audio/x-wavpack' => ['wv', 'wvp'], + 'audio/x-wavpack-correction' => ['wvc'], + 'audio/x-xi' => ['xi'], + 'audio/x-xm' => ['xm'], + 'audio/x-xmf' => ['xmf'], + 'audio/xm' => ['xm'], + 'audio/xmf' => ['xmf'], + 'chemical/x-cdx' => ['cdx'], + 'chemical/x-cif' => ['cif'], + 'chemical/x-cmdf' => ['cmdf'], + 'chemical/x-cml' => ['cml'], + 'chemical/x-csml' => ['csml'], + 'chemical/x-xyz' => ['xyz'], + 'flv-application/octet-stream' => ['flv'], + 'font/collection' => ['ttc'], + 'font/otf' => ['otf'], + 'font/ttf' => ['ttf'], + 'font/woff' => ['woff', 'woff2'], + 'font/woff2' => ['woff2'], + 'image/bmp' => ['bmp', 'dib'], + 'image/cdr' => ['cdr'], + 'image/cgm' => ['cgm'], + 'image/emf' => ['emf'], + 'image/fax-g3' => ['g3'], + 'image/fits' => ['fits'], + 'image/g3fax' => ['g3'], + 'image/gif' => ['gif'], + 'image/heic' => ['heic', 'heif'], + 'image/heic-sequence' => ['heic', 'heif'], + 'image/heif' => ['heic', 'heif'], + 'image/heif-sequence' => ['heic', 'heif'], + 'image/ico' => ['ico'], + 'image/icon' => ['ico'], + 'image/ief' => ['ief'], + 'image/jp2' => ['jp2', 'jpg2'], + 'image/jpeg' => ['jpeg', 'jpg', 'jpe'], + 'image/jpeg2000' => ['jp2', 'jpg2'], + 'image/jpeg2000-image' => ['jp2', 'jpg2'], + 'image/jpm' => ['jpm', 'jpgm'], + 'image/jpx' => ['jpf', 'jpx'], + 'image/ktx' => ['ktx'], + 'image/openraster' => ['ora'], + 'image/pdf' => ['pdf'], + 'image/photoshop' => ['psd'], + 'image/pjpeg' => ['jpeg', 'jpg', 'jpe'], + 'image/png' => ['png'], + 'image/prs.btif' => ['btif'], + 'image/psd' => ['psd'], + 'image/rle' => ['rle'], + 'image/sgi' => ['sgi'], + 'image/svg' => ['svg'], + 'image/svg+xml' => ['svg', 'svgz'], + 'image/svg+xml-compressed' => ['svgz'], + 'image/tiff' => ['tiff', 'tif'], + 'image/vnd.adobe.photoshop' => ['psd'], + 'image/vnd.dece.graphic' => ['uvi', 'uvvi', 'uvg', 'uvvg'], + 'image/vnd.djvu' => ['djvu', 'djv'], + 'image/vnd.djvu+multipage' => ['djvu', 'djv'], + 'image/vnd.dvb.subtitle' => ['sub'], + 'image/vnd.dwg' => ['dwg'], + 'image/vnd.dxf' => ['dxf'], + 'image/vnd.fastbidsheet' => ['fbs'], + 'image/vnd.fpx' => ['fpx'], + 'image/vnd.fst' => ['fst'], + 'image/vnd.fujixerox.edmics-mmr' => ['mmr'], + 'image/vnd.fujixerox.edmics-rlc' => ['rlc'], + 'image/vnd.microsoft.icon' => ['ico'], + 'image/vnd.ms-modi' => ['mdi'], + 'image/vnd.ms-photo' => ['wdp'], + 'image/vnd.net-fpx' => ['npx'], + 'image/vnd.rn-realpix' => ['rp'], + 'image/vnd.wap.wbmp' => ['wbmp'], + 'image/vnd.xiff' => ['xif'], + 'image/vnd.zbrush.pcx' => ['pcx'], + 'image/webp' => ['webp'], + 'image/wmf' => ['wmf'], + 'image/x-3ds' => ['3ds'], + 'image/x-adobe-dng' => ['dng'], + 'image/x-applix-graphics' => ['ag'], + 'image/x-bmp' => ['bmp', 'dib'], + 'image/x-bzeps' => ['eps.bz2', 'epsi.bz2', 'epsf.bz2'], + 'image/x-canon-cr2' => ['cr2'], + 'image/x-canon-crw' => ['crw'], + 'image/x-cdr' => ['cdr'], + 'image/x-cmu-raster' => ['ras'], + 'image/x-cmx' => ['cmx'], + 'image/x-compressed-xcf' => ['xcf.gz', 'xcf.bz2'], + 'image/x-dds' => ['dds'], + 'image/x-djvu' => ['djvu', 'djv'], + 'image/x-emf' => ['emf'], + 'image/x-eps' => ['eps', 'epsi', 'epsf'], + 'image/x-exr' => ['exr'], + 'image/x-fits' => ['fits'], + 'image/x-freehand' => ['fh', 'fhc', 'fh4', 'fh5', 'fh7'], + 'image/x-fuji-raf' => ['raf'], + 'image/x-gimp-gbr' => ['gbr'], + 'image/x-gimp-gih' => ['gih'], + 'image/x-gimp-pat' => ['pat'], + 'image/x-gzeps' => ['eps.gz', 'epsi.gz', 'epsf.gz'], + 'image/x-icb' => ['tga', 'icb', 'tpic', 'vda', 'vst'], + 'image/x-icns' => ['icns'], + 'image/x-ico' => ['ico'], + 'image/x-icon' => ['ico'], + 'image/x-iff' => ['iff', 'ilbm', 'lbm'], + 'image/x-ilbm' => ['iff', 'ilbm', 'lbm'], + 'image/x-jng' => ['jng'], + 'image/x-jp2-codestream' => ['j2c', 'j2k', 'jpc'], + 'image/x-jpeg2000-image' => ['jp2', 'jpg2'], + 'image/x-kodak-dcr' => ['dcr'], + 'image/x-kodak-k25' => ['k25'], + 'image/x-kodak-kdc' => ['kdc'], + 'image/x-lwo' => ['lwo', 'lwob'], + 'image/x-lws' => ['lws'], + 'image/x-macpaint' => ['pntg'], + 'image/x-minolta-mrw' => ['mrw'], + 'image/x-mrsid-image' => ['sid'], + 'image/x-ms-bmp' => ['bmp', 'dib'], + 'image/x-msod' => ['msod'], + 'image/x-nikon-nef' => ['nef'], + 'image/x-olympus-orf' => ['orf'], + 'image/x-panasonic-raw' => ['raw'], + 'image/x-panasonic-raw2' => ['rw2'], + 'image/x-panasonic-rw' => ['raw'], + 'image/x-panasonic-rw2' => ['rw2'], + 'image/x-pcx' => ['pcx'], + 'image/x-pentax-pef' => ['pef'], + 'image/x-photo-cd' => ['pcd'], + 'image/x-photoshop' => ['psd'], + 'image/x-pict' => ['pic', 'pct', 'pict', 'pict1', 'pict2'], + 'image/x-portable-anymap' => ['pnm'], + 'image/x-portable-bitmap' => ['pbm'], + 'image/x-portable-graymap' => ['pgm'], + 'image/x-portable-pixmap' => ['ppm'], + 'image/x-psd' => ['psd'], + 'image/x-quicktime' => ['qtif', 'qif'], + 'image/x-rgb' => ['rgb'], + 'image/x-sgi' => ['sgi'], + 'image/x-sigma-x3f' => ['x3f'], + 'image/x-skencil' => ['sk', 'sk1'], + 'image/x-sony-arw' => ['arw'], + 'image/x-sony-sr2' => ['sr2'], + 'image/x-sony-srf' => ['srf'], + 'image/x-sun-raster' => ['sun'], + 'image/x-tga' => ['tga', 'icb', 'tpic', 'vda', 'vst'], + 'image/x-win-bitmap' => ['cur'], + 'image/x-win-metafile' => ['wmf'], + 'image/x-wmf' => ['wmf'], + 'image/x-xbitmap' => ['xbm'], + 'image/x-xcf' => ['xcf'], + 'image/x-xfig' => ['fig'], + 'image/x-xpixmap' => ['xpm'], + 'image/x-xpm' => ['xpm'], + 'image/x-xwindowdump' => ['xwd'], + 'image/x.djvu' => ['djvu', 'djv'], + 'message/rfc822' => ['eml', 'mime'], + 'model/iges' => ['igs', 'iges'], + 'model/mesh' => ['msh', 'mesh', 'silo'], + 'model/stl' => ['stl'], + 'model/vnd.collada+xml' => ['dae'], + 'model/vnd.dwf' => ['dwf'], + 'model/vnd.gdl' => ['gdl'], + 'model/vnd.gtw' => ['gtw'], + 'model/vnd.mts' => ['mts'], + 'model/vnd.vtu' => ['vtu'], + 'model/vrml' => ['wrl', 'vrml', 'vrm'], + 'model/x.stl-ascii' => ['stl'], + 'model/x.stl-binary' => ['stl'], + 'model/x3d+binary' => ['x3db', 'x3dbz'], + 'model/x3d+vrml' => ['x3dv', 'x3dvz'], + 'model/x3d+xml' => ['x3d', 'x3dz'], + 'text/cache-manifest' => ['appcache', 'manifest'], + 'text/calendar' => ['ics', 'ifb', 'vcs'], + 'text/css' => ['css'], + 'text/csv' => ['csv'], + 'text/csv-schema' => ['csvs'], + 'text/directory' => ['vcard', 'vcf', 'vct', 'gcrd'], + 'text/ecmascript' => ['es'], + 'text/gedcom' => ['ged', 'gedcom'], + 'text/google-video-pointer' => ['gvp'], + 'text/html' => ['html', 'htm'], + 'text/ico' => ['ico'], + 'text/javascript' => ['js', 'jsm', 'mjs'], + 'text/markdown' => ['md', 'mkd', 'markdown'], + 'text/mathml' => ['mml'], + 'text/n3' => ['n3'], + 'text/plain' => ['txt', 'text', 'conf', 'def', 'list', 'log', 'in', 'asc'], + 'text/prs.lines.tag' => ['dsc'], + 'text/rdf' => ['rdf', 'rdfs', 'owl'], + 'text/richtext' => ['rtx'], + 'text/rss' => ['rss'], + 'text/rtf' => ['rtf'], + 'text/rust' => ['rs'], + 'text/sgml' => ['sgml', 'sgm'], + 'text/spreadsheet' => ['sylk', 'slk'], + 'text/tab-separated-values' => ['tsv'], + 'text/troff' => ['t', 'tr', 'roff', 'man', 'me', 'ms'], + 'text/turtle' => ['ttl'], + 'text/uri-list' => ['uri', 'uris', 'urls'], + 'text/vcard' => ['vcard', 'vcf', 'vct', 'gcrd'], + 'text/vnd.curl' => ['curl'], + 'text/vnd.curl.dcurl' => ['dcurl'], + 'text/vnd.curl.mcurl' => ['mcurl'], + 'text/vnd.curl.scurl' => ['scurl'], + 'text/vnd.dvb.subtitle' => ['sub'], + 'text/vnd.fly' => ['fly'], + 'text/vnd.fmi.flexstor' => ['flx'], + 'text/vnd.graphviz' => ['gv', 'dot'], + 'text/vnd.in3d.3dml' => ['3dml'], + 'text/vnd.in3d.spot' => ['spot'], + 'text/vnd.qt.linguist' => ['ts'], + 'text/vnd.rn-realtext' => ['rt'], + 'text/vnd.sun.j2me.app-descriptor' => ['jad'], + 'text/vnd.trolltech.linguist' => ['ts'], + 'text/vnd.wap.wml' => ['wml'], + 'text/vnd.wap.wmlscript' => ['wmls'], + 'text/vtt' => ['vtt'], + 'text/x-adasrc' => ['adb', 'ads'], + 'text/x-asm' => ['s', 'asm'], + 'text/x-bibtex' => ['bib'], + 'text/x-c' => ['c', 'cc', 'cxx', 'cpp', 'h', 'hh', 'dic'], + 'text/x-c++hdr' => ['hh', 'hp', 'hpp', 'h++', 'hxx'], + 'text/x-c++src' => ['cpp', 'cxx', 'cc', 'C', 'c++'], + 'text/x-chdr' => ['h'], + 'text/x-cmake' => ['cmake'], + 'text/x-cobol' => ['cbl', 'cob'], + 'text/x-comma-separated-values' => ['csv'], + 'text/x-csharp' => ['cs'], + 'text/x-csrc' => ['c'], + 'text/x-csv' => ['csv'], + 'text/x-dbus-service' => ['service'], + 'text/x-dcl' => ['dcl'], + 'text/x-diff' => ['diff', 'patch'], + 'text/x-dsl' => ['dsl'], + 'text/x-dsrc' => ['d', 'di'], + 'text/x-dtd' => ['dtd'], + 'text/x-eiffel' => ['e', 'eif'], + 'text/x-emacs-lisp' => ['el'], + 'text/x-erlang' => ['erl'], + 'text/x-fortran' => ['f', 'for', 'f77', 'f90', 'f95'], + 'text/x-genie' => ['gs'], + 'text/x-gettext-translation' => ['po'], + 'text/x-gettext-translation-template' => ['pot'], + 'text/x-gherkin' => ['feature'], + 'text/x-go' => ['go'], + 'text/x-google-video-pointer' => ['gvp'], + 'text/x-haskell' => ['hs'], + 'text/x-idl' => ['idl'], + 'text/x-imelody' => ['imy', 'ime'], + 'text/x-iptables' => ['iptables'], + 'text/x-java' => ['java'], + 'text/x-java-source' => ['java'], + 'text/x-ldif' => ['ldif'], + 'text/x-lilypond' => ['ly'], + 'text/x-literate-haskell' => ['lhs'], + 'text/x-log' => ['log'], + 'text/x-lua' => ['lua'], + 'text/x-lyx' => ['lyx'], + 'text/x-makefile' => ['mk', 'mak'], + 'text/x-markdown' => ['md', 'mkd', 'markdown'], + 'text/x-matlab' => ['m'], + 'text/x-microdvd' => ['sub'], + 'text/x-moc' => ['moc'], + 'text/x-modelica' => ['mo'], + 'text/x-mof' => ['mof'], + 'text/x-mpsub' => ['sub'], + 'text/x-mrml' => ['mrml', 'mrl'], + 'text/x-ms-regedit' => ['reg'], + 'text/x-mup' => ['mup', 'not'], + 'text/x-nfo' => ['nfo'], + 'text/x-objcsrc' => ['m'], + 'text/x-ocaml' => ['ml', 'mli'], + 'text/x-ocl' => ['ocl'], + 'text/x-octave' => ['m'], + 'text/x-ooc' => ['ooc'], + 'text/x-opencl-src' => ['cl'], + 'text/x-opml' => ['opml'], + 'text/x-opml+xml' => ['opml'], + 'text/x-pascal' => ['p', 'pas'], + 'text/x-patch' => ['diff', 'patch'], + 'text/x-perl' => ['pl', 'PL', 'pm', 'al', 'perl', 'pod', 't'], + 'text/x-po' => ['po'], + 'text/x-pot' => ['pot'], + 'text/x-python' => ['py', 'pyx', 'wsgi'], + 'text/x-python3' => ['py', 'py3', 'py3x'], + 'text/x-qml' => ['qml', 'qmltypes', 'qmlproject'], + 'text/x-reject' => ['rej'], + 'text/x-rpm-spec' => ['spec'], + 'text/x-sass' => ['sass'], + 'text/x-scala' => ['scala'], + 'text/x-scheme' => ['scm', 'ss'], + 'text/x-scss' => ['scss'], + 'text/x-setext' => ['etx'], + 'text/x-sfv' => ['sfv'], + 'text/x-sh' => ['sh'], + 'text/x-sql' => ['sql'], + 'text/x-ssa' => ['ssa', 'ass'], + 'text/x-subviewer' => ['sub'], + 'text/x-svhdr' => ['svh'], + 'text/x-svsrc' => ['sv'], + 'text/x-systemd-unit' => ['automount', 'device', 'mount', 'path', 'scope', 'service', 'slice', 'socket', 'swap', 'target', 'timer'], + 'text/x-tcl' => ['tcl', 'tk'], + 'text/x-tex' => ['tex', 'ltx', 'sty', 'cls', 'dtx', 'ins', 'latex'], + 'text/x-texinfo' => ['texi', 'texinfo'], + 'text/x-troff' => ['tr', 'roff', 't'], + 'text/x-troff-me' => ['me'], + 'text/x-troff-mm' => ['mm'], + 'text/x-troff-ms' => ['ms'], + 'text/x-twig' => ['twig'], + 'text/x-txt2tags' => ['t2t'], + 'text/x-uil' => ['uil'], + 'text/x-uuencode' => ['uu', 'uue'], + 'text/x-vala' => ['vala', 'vapi'], + 'text/x-vcalendar' => ['vcs', 'ics'], + 'text/x-vcard' => ['vcf', 'vcard', 'vct', 'gcrd'], + 'text/x-verilog' => ['v'], + 'text/x-vhdl' => ['vhd', 'vhdl'], + 'text/x-xmi' => ['xmi'], + 'text/x-xslfo' => ['fo', 'xslfo'], + 'text/x-yaml' => ['yaml', 'yml'], + 'text/x.gcode' => ['gcode'], + 'text/xml' => ['xml', 'xbl', 'xsd', 'rng'], + 'text/xml-external-parsed-entity' => ['ent'], + 'text/yaml' => ['yaml', 'yml'], + 'video/3gp' => ['3gp', '3gpp', '3ga'], + 'video/3gpp' => ['3gp', '3gpp', '3ga'], + 'video/3gpp-encrypted' => ['3gp', '3gpp', '3ga'], + 'video/3gpp2' => ['3g2', '3gp2', '3gpp2'], + 'video/annodex' => ['axv'], + 'video/avi' => ['avi', 'avf', 'divx'], + 'video/divx' => ['avi', 'avf', 'divx'], + 'video/dv' => ['dv'], + 'video/fli' => ['fli', 'flc'], + 'video/flv' => ['flv'], + 'video/h261' => ['h261'], + 'video/h263' => ['h263'], + 'video/h264' => ['h264'], + 'video/jpeg' => ['jpgv'], + 'video/jpm' => ['jpm', 'jpgm'], + 'video/mj2' => ['mj2', 'mjp2'], + 'video/mp2t' => ['m2t', 'm2ts', 'ts', 'mts', 'cpi', 'clpi', 'mpl', 'mpls', 'bdm', 'bdmv'], + 'video/mp4' => ['mp4', 'mp4v', 'mpg4', 'm4v', 'f4v', 'lrv'], + 'video/mp4v-es' => ['mp4', 'm4v', 'f4v', 'lrv'], + 'video/mpeg' => ['mpeg', 'mpg', 'mpe', 'm1v', 'm2v', 'mp2', 'vob'], + 'video/mpeg-system' => ['mpeg', 'mpg', 'mp2', 'mpe', 'vob'], + 'video/msvideo' => ['avi', 'avf', 'divx'], + 'video/ogg' => ['ogv', 'ogg'], + 'video/quicktime' => ['qt', 'mov', 'moov', 'qtvr'], + 'video/vivo' => ['viv', 'vivo'], + 'video/vnd.dece.hd' => ['uvh', 'uvvh'], + 'video/vnd.dece.mobile' => ['uvm', 'uvvm'], + 'video/vnd.dece.pd' => ['uvp', 'uvvp'], + 'video/vnd.dece.sd' => ['uvs', 'uvvs'], + 'video/vnd.dece.video' => ['uvv', 'uvvv'], + 'video/vnd.divx' => ['avi', 'avf', 'divx'], + 'video/vnd.dvb.file' => ['dvb'], + 'video/vnd.fvt' => ['fvt'], + 'video/vnd.mpegurl' => ['mxu', 'm4u', 'm1u'], + 'video/vnd.ms-playready.media.pyv' => ['pyv'], + 'video/vnd.rn-realvideo' => ['rv', 'rvx'], + 'video/vnd.uvvu.mp4' => ['uvu', 'uvvu'], + 'video/vnd.vivo' => ['viv', 'vivo'], + 'video/webm' => ['webm'], + 'video/x-anim' => ['anim[1-9j]'], + 'video/x-annodex' => ['axv'], + 'video/x-avi' => ['avi', 'avf', 'divx'], + 'video/x-f4v' => ['f4v'], + 'video/x-fli' => ['fli', 'flc'], + 'video/x-flic' => ['fli', 'flc'], + 'video/x-flv' => ['flv'], + 'video/x-javafx' => ['fxm'], + 'video/x-m4v' => ['m4v', 'mp4', 'f4v', 'lrv'], + 'video/x-matroska' => ['mkv', 'mk3d', 'mks'], + 'video/x-matroska-3d' => ['mk3d'], + 'video/x-mjpeg' => ['mjpeg', 'mjpg'], + 'video/x-mng' => ['mng'], + 'video/x-mpeg' => ['mpeg', 'mpg', 'mp2', 'mpe', 'vob'], + 'video/x-mpeg-system' => ['mpeg', 'mpg', 'mp2', 'mpe', 'vob'], + 'video/x-mpeg2' => ['mpeg', 'mpg', 'mp2', 'mpe', 'vob'], + 'video/x-mpegurl' => ['m1u', 'm4u', 'mxu'], + 'video/x-ms-asf' => ['asf', 'asx'], + 'video/x-ms-asf-plugin' => ['asf'], + 'video/x-ms-vob' => ['vob'], + 'video/x-ms-wax' => ['asx', 'wax', 'wvx', 'wmx'], + 'video/x-ms-wm' => ['wm', 'asf'], + 'video/x-ms-wmv' => ['wmv'], + 'video/x-ms-wmx' => ['wmx', 'asx', 'wax', 'wvx'], + 'video/x-ms-wvx' => ['wvx', 'asx', 'wax', 'wmx'], + 'video/x-msvideo' => ['avi', 'avf', 'divx'], + 'video/x-nsv' => ['nsv'], + 'video/x-ogg' => ['ogv', 'ogg'], + 'video/x-ogm' => ['ogm'], + 'video/x-ogm+ogg' => ['ogm'], + 'video/x-real-video' => ['rv', 'rvx'], + 'video/x-sgi-movie' => ['movie'], + 'video/x-smv' => ['smv'], + 'video/x-theora' => ['ogg'], + 'video/x-theora+ogg' => ['ogg'], + 'x-conference/x-cooltalk' => ['ice'], + 'x-epoc/x-sisx-app' => ['sisx'], + 'zz-application/zz-winassoc-123' => ['123', 'wk1', 'wk3', 'wk4', 'wks'], + 'zz-application/zz-winassoc-cab' => ['cab'], + 'zz-application/zz-winassoc-cdr' => ['cdr'], + 'zz-application/zz-winassoc-doc' => ['doc'], + 'zz-application/zz-winassoc-hlp' => ['hlp'], + 'zz-application/zz-winassoc-mdb' => ['mdb'], + 'zz-application/zz-winassoc-uu' => ['uue'], + 'zz-application/zz-winassoc-xls' => ['xls', 'xlc', 'xll', 'xlm', 'xlw', 'xla', 'xlt', 'xld'], + ]; + + private static $reverseMap = [ + '32x' => ['application/x-genesis-32x-rom'], + '3dml' => ['text/vnd.in3d.3dml'], + '3ds' => ['image/x-3ds'], + '3g2' => ['audio/3gpp2', 'video/3gpp2'], + '3ga' => ['audio/3gpp', 'audio/3gpp-encrypted', 'audio/x-rn-3gpp-amr', 'audio/x-rn-3gpp-amr-encrypted', 'audio/x-rn-3gpp-amr-wb', 'audio/x-rn-3gpp-amr-wb-encrypted', 'video/3gp', 'video/3gpp', 'video/3gpp-encrypted'], + '3gp' => ['audio/3gpp', 'audio/3gpp-encrypted', 'audio/x-rn-3gpp-amr', 'audio/x-rn-3gpp-amr-encrypted', 'audio/x-rn-3gpp-amr-wb', 'audio/x-rn-3gpp-amr-wb-encrypted', 'video/3gp', 'video/3gpp', 'video/3gpp-encrypted'], + '3gp2' => ['audio/3gpp2', 'video/3gpp2'], + '3gpp' => ['audio/3gpp', 'audio/3gpp-encrypted', 'audio/x-rn-3gpp-amr', 'audio/x-rn-3gpp-amr-encrypted', 'audio/x-rn-3gpp-amr-wb', 'audio/x-rn-3gpp-amr-wb-encrypted', 'video/3gp', 'video/3gpp', 'video/3gpp-encrypted'], + '3gpp2' => ['audio/3gpp2', 'video/3gpp2'], + '7z' => ['application/x-7z-compressed'], + 'BLEND' => ['application/x-blender'], + 'C' => ['text/x-c++src'], + 'PAR2' => ['application/x-par2'], + 'PL' => ['application/x-perl', 'text/x-perl'], + 'Z' => ['application/x-compress'], + 'a' => ['application/x-archive'], + 'a26' => ['application/x-atari-2600-rom'], + 'a78' => ['application/x-atari-7800-rom'], + 'aa' => ['audio/vnd.audible', 'audio/vnd.audible.aax', 'audio/x-pn-audibleaudio'], + 'aab' => ['application/x-authorware-bin'], + 'aac' => ['audio/aac', 'audio/x-aac', 'audio/x-hx-aac-adts'], + 'aam' => ['application/x-authorware-map'], + 'aas' => ['application/x-authorware-seg'], + 'aax' => ['audio/vnd.audible', 'audio/vnd.audible.aax', 'audio/x-pn-audibleaudio'], + 'abw' => ['application/x-abiword'], + 'abw.CRASHED' => ['application/x-abiword'], + 'abw.gz' => ['application/x-abiword'], + 'ac' => ['application/pkix-attr-cert'], + 'ac3' => ['audio/ac3'], + 'acc' => ['application/vnd.americandynamics.acc'], + 'ace' => ['application/x-ace', 'application/x-ace-compressed'], + 'acu' => ['application/vnd.acucobol'], + 'acutc' => ['application/vnd.acucorp'], + 'adb' => ['text/x-adasrc'], + 'adf' => ['application/x-amiga-disk-format'], + 'adp' => ['audio/adpcm'], + 'ads' => ['text/x-adasrc'], + 'adts' => ['audio/aac', 'audio/x-aac', 'audio/x-hx-aac-adts'], + 'aep' => ['application/vnd.audiograph'], + 'afm' => ['application/x-font-afm', 'application/x-font-type1'], + 'afp' => ['application/vnd.ibm.modcap'], + 'ag' => ['image/x-applix-graphics'], + 'agb' => ['application/x-gba-rom'], + 'ahead' => ['application/vnd.ahead.space'], + 'ai' => ['application/illustrator', 'application/postscript', 'application/vnd.adobe.illustrator'], + 'aif' => ['audio/x-aiff'], + 'aifc' => ['audio/x-aifc', 'audio/x-aiff', 'audio/x-aiffc'], + 'aiff' => ['audio/x-aiff'], + 'aiffc' => ['audio/x-aifc', 'audio/x-aiffc'], + 'air' => ['application/vnd.adobe.air-application-installer-package+zip'], + 'ait' => ['application/vnd.dvb.ait'], + 'al' => ['application/x-perl', 'text/x-perl'], + 'alz' => ['application/x-alz'], + 'ami' => ['application/vnd.amiga.ami'], + 'amr' => ['audio/amr', 'audio/amr-encrypted'], + 'amz' => ['audio/x-amzxml'], + 'ani' => ['application/x-navi-animation'], + 'anim[1-9j]' => ['video/x-anim'], + 'anx' => ['application/annodex', 'application/x-annodex'], + 'ape' => ['audio/x-ape'], + 'apk' => ['application/vnd.android.package-archive'], + 'appcache' => ['text/cache-manifest'], + 'appimage' => ['application/vnd.appimage', 'application/x-iso9660-appimage'], + 'application' => ['application/x-ms-application'], + 'apr' => ['application/vnd.lotus-approach'], + 'aps' => ['application/postscript'], + 'ar' => ['application/x-archive'], + 'arc' => ['application/x-freearc'], + 'arj' => ['application/x-arj'], + 'arw' => ['image/x-sony-arw'], + 'as' => ['application/x-applix-spreadsheet'], + 'asc' => ['application/pgp', 'application/pgp-encrypted', 'application/pgp-keys', 'application/pgp-signature', 'text/plain'], + 'asf' => ['application/vnd.ms-asf', 'video/x-ms-asf', 'video/x-ms-asf-plugin', 'video/x-ms-wm'], + 'asm' => ['text/x-asm'], + 'aso' => ['application/vnd.accpac.simply.aso'], + 'asp' => ['application/x-asp'], + 'ass' => ['audio/aac', 'audio/x-aac', 'audio/x-hx-aac-adts', 'text/x-ssa'], + 'asx' => ['application/x-ms-asx', 'audio/x-ms-asx', 'video/x-ms-asf', 'video/x-ms-wax', 'video/x-ms-wmx', 'video/x-ms-wvx'], + 'atc' => ['application/vnd.acucorp'], + 'atom' => ['application/atom+xml'], + 'atomcat' => ['application/atomcat+xml'], + 'atomsvc' => ['application/atomsvc+xml'], + 'atx' => ['application/vnd.antix.game-component'], + 'au' => ['audio/basic'], + 'automount' => ['text/x-systemd-unit'], + 'avf' => ['video/avi', 'video/divx', 'video/msvideo', 'video/vnd.divx', 'video/x-avi', 'video/x-msvideo'], + 'avi' => ['video/avi', 'video/divx', 'video/msvideo', 'video/vnd.divx', 'video/x-avi', 'video/x-msvideo'], + 'aw' => ['application/applixware', 'application/x-applix-word'], + 'awb' => ['audio/amr-wb', 'audio/amr-wb-encrypted'], + 'awk' => ['application/x-awk'], + 'axa' => ['audio/annodex', 'audio/x-annodex'], + 'axv' => ['video/annodex', 'video/x-annodex'], + 'azf' => ['application/vnd.airzip.filesecure.azf'], + 'azs' => ['application/vnd.airzip.filesecure.azs'], + 'azw' => ['application/vnd.amazon.ebook'], + 'bak' => ['application/x-trash'], + 'bat' => ['application/x-msdownload'], + 'bcpio' => ['application/x-bcpio'], + 'bdf' => ['application/x-font-bdf'], + 'bdm' => ['application/vnd.syncml.dm+wbxml', 'video/mp2t'], + 'bdmv' => ['video/mp2t'], + 'bed' => ['application/vnd.realvnc.bed'], + 'bh2' => ['application/vnd.fujitsu.oasysprs'], + 'bib' => ['text/x-bibtex'], + 'bin' => ['application/octet-stream', 'application/x-saturn-rom', 'application/x-sega-cd-rom'], + 'blb' => ['application/x-blorb'], + 'blend' => ['application/x-blender'], + 'blender' => ['application/x-blender'], + 'blorb' => ['application/x-blorb'], + 'bmi' => ['application/vnd.bmi'], + 'bmp' => ['image/bmp', 'image/x-bmp', 'image/x-ms-bmp'], + 'book' => ['application/vnd.framemaker'], + 'box' => ['application/vnd.previewsystems.box'], + 'boz' => ['application/x-bzip2'], + 'bpk' => ['application/octet-stream'], + 'bsdiff' => ['application/x-bsdiff'], + 'btif' => ['image/prs.btif'], + 'bz' => ['application/x-bzip', 'application/x-bzip2'], + 'bz2' => ['application/x-bz2', 'application/x-bzip', 'application/x-bzip2'], + 'c' => ['text/x-c', 'text/x-csrc'], + 'c++' => ['text/x-c++src'], + 'c11amc' => ['application/vnd.cluetrust.cartomobile-config'], + 'c11amz' => ['application/vnd.cluetrust.cartomobile-config-pkg'], + 'c4d' => ['application/vnd.clonk.c4group'], + 'c4f' => ['application/vnd.clonk.c4group'], + 'c4g' => ['application/vnd.clonk.c4group'], + 'c4p' => ['application/vnd.clonk.c4group'], + 'c4u' => ['application/vnd.clonk.c4group'], + 'cab' => ['application/vnd.ms-cab-compressed', 'zz-application/zz-winassoc-cab'], + 'caf' => ['audio/x-caf'], + 'cap' => ['application/pcap', 'application/vnd.tcpdump.pcap', 'application/x-pcap'], + 'car' => ['application/vnd.curl.car'], + 'cat' => ['application/vnd.ms-pki.seccat'], + 'cb7' => ['application/x-cb7', 'application/x-cbr'], + 'cba' => ['application/x-cbr'], + 'cbl' => ['text/x-cobol'], + 'cbr' => ['application/vnd.comicbook-rar', 'application/x-cbr'], + 'cbt' => ['application/x-cbr', 'application/x-cbt'], + 'cbz' => ['application/vnd.comicbook+zip', 'application/x-cbr', 'application/x-cbz'], + 'cc' => ['text/x-c', 'text/x-c++src'], + 'ccmx' => ['application/x-ccmx'], + 'cct' => ['application/x-director'], + 'ccxml' => ['application/ccxml+xml'], + 'cdbcmsg' => ['application/vnd.contact.cmsg'], + 'cdf' => ['application/x-netcdf'], + 'cdkey' => ['application/vnd.mediastation.cdkey'], + 'cdmia' => ['application/cdmi-capability'], + 'cdmic' => ['application/cdmi-container'], + 'cdmid' => ['application/cdmi-domain'], + 'cdmio' => ['application/cdmi-object'], + 'cdmiq' => ['application/cdmi-queue'], + 'cdr' => ['application/cdr', 'application/coreldraw', 'application/vnd.corel-draw', 'application/x-cdr', 'application/x-coreldraw', 'image/cdr', 'image/x-cdr', 'zz-application/zz-winassoc-cdr'], + 'cdx' => ['chemical/x-cdx'], + 'cdxml' => ['application/vnd.chemdraw+xml'], + 'cdy' => ['application/vnd.cinderella'], + 'cer' => ['application/pkix-cert'], + 'cert' => ['application/x-x509-ca-cert'], + 'cfs' => ['application/x-cfs-compressed'], + 'cgb' => ['application/x-gameboy-color-rom'], + 'cgm' => ['image/cgm'], + 'chat' => ['application/x-chat'], + 'chm' => ['application/vnd.ms-htmlhelp', 'application/x-chm'], + 'chrt' => ['application/vnd.kde.kchart', 'application/x-kchart'], + 'cif' => ['chemical/x-cif'], + 'cii' => ['application/vnd.anser-web-certificate-issue-initiation'], + 'cil' => ['application/vnd.ms-artgalry'], + 'cl' => ['text/x-opencl-src'], + 'cla' => ['application/vnd.claymore'], + 'class' => ['application/java', 'application/java-byte-code', 'application/java-vm', 'application/x-java', 'application/x-java-class', 'application/x-java-vm'], + 'clkk' => ['application/vnd.crick.clicker.keyboard'], + 'clkp' => ['application/vnd.crick.clicker.palette'], + 'clkt' => ['application/vnd.crick.clicker.template'], + 'clkw' => ['application/vnd.crick.clicker.wordbank'], + 'clkx' => ['application/vnd.crick.clicker'], + 'clp' => ['application/x-msclip'], + 'clpi' => ['video/mp2t'], + 'cls' => ['application/x-tex', 'text/x-tex'], + 'cmake' => ['text/x-cmake'], + 'cmc' => ['application/vnd.cosmocaller'], + 'cmdf' => ['chemical/x-cmdf'], + 'cml' => ['chemical/x-cml'], + 'cmp' => ['application/vnd.yellowriver-custom-menu'], + 'cmx' => ['image/x-cmx'], + 'cob' => ['text/x-cobol'], + 'cod' => ['application/vnd.rim.cod'], + 'coffee' => ['application/vnd.coffeescript'], + 'com' => ['application/x-msdownload'], + 'conf' => ['text/plain'], + 'cpi' => ['video/mp2t'], + 'cpio' => ['application/x-cpio'], + 'cpio.gz' => ['application/x-cpio-compressed'], + 'cpp' => ['text/x-c', 'text/x-c++src'], + 'cpt' => ['application/mac-compactpro'], + 'cr2' => ['image/x-canon-cr2'], + 'crd' => ['application/x-mscardfile'], + 'crdownload' => ['application/x-partial-download'], + 'crl' => ['application/pkix-crl'], + 'crt' => ['application/x-x509-ca-cert'], + 'crw' => ['image/x-canon-crw'], + 'cryptonote' => ['application/vnd.rig.cryptonote'], + 'cs' => ['text/x-csharp'], + 'csh' => ['application/x-csh'], + 'csml' => ['chemical/x-csml'], + 'csp' => ['application/vnd.commonspace'], + 'css' => ['text/css'], + 'cst' => ['application/x-director'], + 'csv' => ['text/csv', 'text/x-comma-separated-values', 'text/x-csv'], + 'csvs' => ['text/csv-schema'], + 'cu' => ['application/cu-seeme'], + 'cue' => ['application/x-cue'], + 'cur' => ['image/x-win-bitmap'], + 'curl' => ['text/vnd.curl'], + 'cww' => ['application/prs.cww'], + 'cxt' => ['application/x-director'], + 'cxx' => ['text/x-c', 'text/x-c++src'], + 'd' => ['text/x-dsrc'], + 'dae' => ['model/vnd.collada+xml'], + 'daf' => ['application/vnd.mobius.daf'], + 'dar' => ['application/x-dar'], + 'dart' => ['application/vnd.dart'], + 'dataless' => ['application/vnd.fdsn.seed'], + 'davmount' => ['application/davmount+xml'], + 'dbf' => ['application/dbase', 'application/dbf', 'application/x-dbase', 'application/x-dbf'], + 'dbk' => ['application/docbook+xml', 'application/vnd.oasis.docbook+xml', 'application/x-docbook+xml'], + 'dc' => ['application/x-dc-rom'], + 'dcl' => ['text/x-dcl'], + 'dcm' => ['application/dicom'], + 'dcr' => ['application/x-director', 'image/x-kodak-dcr'], + 'dcurl' => ['text/vnd.curl.dcurl'], + 'dd2' => ['application/vnd.oma.dd2+xml'], + 'ddd' => ['application/vnd.fujixerox.ddd'], + 'dds' => ['image/x-dds'], + 'deb' => ['application/vnd.debian.binary-package', 'application/x-deb', 'application/x-debian-package'], + 'def' => ['text/plain'], + 'deploy' => ['application/octet-stream'], + 'der' => ['application/x-x509-ca-cert'], + 'desktop' => ['application/x-desktop', 'application/x-gnome-app-info'], + 'device' => ['text/x-systemd-unit'], + 'dfac' => ['application/vnd.dreamfactory'], + 'dgc' => ['application/x-dgc-compressed'], + 'di' => ['text/x-dsrc'], + 'dia' => ['application/x-dia-diagram'], + 'dib' => ['image/bmp', 'image/x-bmp', 'image/x-ms-bmp'], + 'dic' => ['text/x-c'], + 'diff' => ['text/x-diff', 'text/x-patch'], + 'dir' => ['application/x-director'], + 'dis' => ['application/vnd.mobius.dis'], + 'dist' => ['application/octet-stream'], + 'distz' => ['application/octet-stream'], + 'divx' => ['video/avi', 'video/divx', 'video/msvideo', 'video/vnd.divx', 'video/x-avi', 'video/x-msvideo'], + 'djv' => ['image/vnd.djvu', 'image/vnd.djvu+multipage', 'image/x-djvu', 'image/x.djvu'], + 'djvu' => ['image/vnd.djvu', 'image/vnd.djvu+multipage', 'image/x-djvu', 'image/x.djvu'], + 'dll' => ['application/x-msdownload'], + 'dmg' => ['application/x-apple-diskimage'], + 'dmp' => ['application/pcap', 'application/vnd.tcpdump.pcap', 'application/x-pcap'], + 'dms' => ['application/octet-stream'], + 'dna' => ['application/vnd.dna'], + 'dng' => ['image/x-adobe-dng'], + 'doc' => ['application/msword', 'application/vnd.ms-word', 'application/x-msword', 'zz-application/zz-winassoc-doc'], + 'docbook' => ['application/docbook+xml', 'application/vnd.oasis.docbook+xml', 'application/x-docbook+xml'], + 'docm' => ['application/vnd.ms-word.document.macroenabled.12'], + 'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'], + 'dot' => ['application/msword', 'application/msword-template', 'text/vnd.graphviz'], + 'dotm' => ['application/vnd.ms-word.template.macroenabled.12'], + 'dotx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.template'], + 'dp' => ['application/vnd.osgi.dp'], + 'dpg' => ['application/vnd.dpgraph'], + 'dra' => ['audio/vnd.dra'], + 'dsc' => ['text/prs.lines.tag'], + 'dsl' => ['text/x-dsl'], + 'dssc' => ['application/dssc+der'], + 'dtb' => ['application/x-dtbook+xml'], + 'dtd' => ['application/xml-dtd', 'text/x-dtd'], + 'dts' => ['audio/vnd.dts', 'audio/x-dts'], + 'dtshd' => ['audio/vnd.dts.hd', 'audio/x-dtshd'], + 'dtx' => ['application/x-tex', 'text/x-tex'], + 'dump' => ['application/octet-stream'], + 'dv' => ['video/dv'], + 'dvb' => ['video/vnd.dvb.file'], + 'dvi' => ['application/x-dvi'], + 'dvi.bz2' => ['application/x-bzdvi'], + 'dvi.gz' => ['application/x-gzdvi'], + 'dwf' => ['model/vnd.dwf'], + 'dwg' => ['image/vnd.dwg'], + 'dxf' => ['image/vnd.dxf'], + 'dxp' => ['application/vnd.spotfire.dxp'], + 'dxr' => ['application/x-director'], + 'e' => ['text/x-eiffel'], + 'ecelp4800' => ['audio/vnd.nuera.ecelp4800'], + 'ecelp7470' => ['audio/vnd.nuera.ecelp7470'], + 'ecelp9600' => ['audio/vnd.nuera.ecelp9600'], + 'ecma' => ['application/ecmascript'], + 'edm' => ['application/vnd.novadigm.edm'], + 'edx' => ['application/vnd.novadigm.edx'], + 'efif' => ['application/vnd.picsel'], + 'egon' => ['application/x-egon'], + 'ei6' => ['application/vnd.pg.osasli'], + 'eif' => ['text/x-eiffel'], + 'el' => ['text/x-emacs-lisp'], + 'elc' => ['application/octet-stream'], + 'emf' => ['application/emf', 'application/x-emf', 'application/x-msmetafile', 'image/emf', 'image/x-emf'], + 'eml' => ['message/rfc822'], + 'emma' => ['application/emma+xml'], + 'emp' => ['application/vnd.emusic-emusic_package'], + 'emz' => ['application/x-msmetafile'], + 'ent' => ['application/xml-external-parsed-entity', 'text/xml-external-parsed-entity'], + 'eol' => ['audio/vnd.digital-winds'], + 'eot' => ['application/vnd.ms-fontobject'], + 'eps' => ['application/postscript', 'image/x-eps'], + 'eps.bz2' => ['image/x-bzeps'], + 'eps.gz' => ['image/x-gzeps'], + 'epsf' => ['image/x-eps'], + 'epsf.bz2' => ['image/x-bzeps'], + 'epsf.gz' => ['image/x-gzeps'], + 'epsi' => ['image/x-eps'], + 'epsi.bz2' => ['image/x-bzeps'], + 'epsi.gz' => ['image/x-gzeps'], + 'epub' => ['application/epub+zip'], + 'erl' => ['text/x-erlang'], + 'es' => ['application/ecmascript', 'text/ecmascript'], + 'es3' => ['application/vnd.eszigno3+xml'], + 'esa' => ['application/vnd.osgi.subsystem'], + 'esf' => ['application/vnd.epson.esf'], + 'et3' => ['application/vnd.eszigno3+xml'], + 'etheme' => ['application/x-e-theme'], + 'etx' => ['text/x-setext'], + 'eva' => ['application/x-eva'], + 'evy' => ['application/x-envoy'], + 'exe' => ['application/x-ms-dos-executable', 'application/x-msdownload'], + 'exi' => ['application/exi'], + 'exr' => ['image/x-exr'], + 'ext' => ['application/vnd.novadigm.ext'], + 'ez' => ['application/andrew-inset'], + 'ez2' => ['application/vnd.ezpix-album'], + 'ez3' => ['application/vnd.ezpix-package'], + 'f' => ['text/x-fortran'], + 'f4a' => ['audio/m4a', 'audio/mp4', 'audio/x-m4a'], + 'f4b' => ['audio/x-m4b'], + 'f4v' => ['video/mp4', 'video/mp4v-es', 'video/x-f4v', 'video/x-m4v'], + 'f77' => ['text/x-fortran'], + 'f90' => ['text/x-fortran'], + 'f95' => ['text/x-fortran'], + 'fb2' => ['application/x-fictionbook', 'application/x-fictionbook+xml'], + 'fb2.zip' => ['application/x-zip-compressed-fb2'], + 'fbs' => ['image/vnd.fastbidsheet'], + 'fcdt' => ['application/vnd.adobe.formscentral.fcdt'], + 'fcs' => ['application/vnd.isac.fcs'], + 'fd' => ['application/x-fd-file', 'application/x-raw-floppy-disk-image'], + 'fdf' => ['application/vnd.fdf'], + 'fds' => ['application/x-fds-disk'], + 'fe_launch' => ['application/vnd.denovo.fcselayout-link'], + 'feature' => ['text/x-gherkin'], + 'fg5' => ['application/vnd.fujitsu.oasysgp'], + 'fgd' => ['application/x-director'], + 'fh' => ['image/x-freehand'], + 'fh4' => ['image/x-freehand'], + 'fh5' => ['image/x-freehand'], + 'fh7' => ['image/x-freehand'], + 'fhc' => ['image/x-freehand'], + 'fig' => ['application/x-xfig', 'image/x-xfig'], + 'fits' => ['image/fits', 'image/x-fits'], + 'fl' => ['application/x-fluid'], + 'flac' => ['audio/flac', 'audio/x-flac'], + 'flatpak' => ['application/vnd.flatpak', 'application/vnd.xdgapp'], + 'flatpakref' => ['application/vnd.flatpak.ref'], + 'flatpakrepo' => ['application/vnd.flatpak.repo'], + 'flc' => ['video/fli', 'video/x-fli', 'video/x-flic'], + 'fli' => ['video/fli', 'video/x-fli', 'video/x-flic'], + 'flo' => ['application/vnd.micrografx.flo'], + 'flv' => ['video/x-flv', 'application/x-flash-video', 'flv-application/octet-stream', 'video/flv'], + 'flw' => ['application/vnd.kde.kivio', 'application/x-kivio'], + 'flx' => ['text/vnd.fmi.flexstor'], + 'fly' => ['text/vnd.fly'], + 'fm' => ['application/vnd.framemaker', 'application/x-frame'], + 'fnc' => ['application/vnd.frogans.fnc'], + 'fo' => ['text/x-xslfo'], + 'fodg' => ['application/vnd.oasis.opendocument.graphics-flat-xml'], + 'fodp' => ['application/vnd.oasis.opendocument.presentation-flat-xml'], + 'fods' => ['application/vnd.oasis.opendocument.spreadsheet-flat-xml'], + 'fodt' => ['application/vnd.oasis.opendocument.text-flat-xml'], + 'for' => ['text/x-fortran'], + 'fpx' => ['image/vnd.fpx'], + 'frame' => ['application/vnd.framemaker'], + 'fsc' => ['application/vnd.fsc.weblaunch'], + 'fst' => ['image/vnd.fst'], + 'ftc' => ['application/vnd.fluxtime.clip'], + 'fti' => ['application/vnd.anser-web-funds-transfer-initiation'], + 'fvt' => ['video/vnd.fvt'], + 'fxm' => ['video/x-javafx'], + 'fxp' => ['application/vnd.adobe.fxp'], + 'fxpl' => ['application/vnd.adobe.fxp'], + 'fzs' => ['application/vnd.fuzzysheet'], + 'g2w' => ['application/vnd.geoplan'], + 'g3' => ['image/fax-g3', 'image/g3fax'], + 'g3w' => ['application/vnd.geospace'], + 'gac' => ['application/vnd.groove-account'], + 'gam' => ['application/x-tads'], + 'gb' => ['application/x-gameboy-rom'], + 'gba' => ['application/x-gba-rom'], + 'gbc' => ['application/x-gameboy-color-rom'], + 'gbr' => ['application/rpki-ghostbusters', 'image/x-gimp-gbr'], + 'gca' => ['application/x-gca-compressed'], + 'gcode' => ['text/x.gcode'], + 'gcrd' => ['text/directory', 'text/vcard', 'text/x-vcard'], + 'gdl' => ['model/vnd.gdl'], + 'ged' => ['application/x-gedcom', 'text/gedcom'], + 'gedcom' => ['application/x-gedcom', 'text/gedcom'], + 'gem' => ['application/x-gtar', 'application/x-tar'], + 'gen' => ['application/x-genesis-rom'], + 'geo' => ['application/vnd.dynageo'], + 'geo.json' => ['application/geo+json', 'application/vnd.geo+json'], + 'geojson' => ['application/geo+json', 'application/vnd.geo+json'], + 'gex' => ['application/vnd.geometry-explorer'], + 'gf' => ['application/x-tex-gf'], + 'gg' => ['application/x-gamegear-rom'], + 'ggb' => ['application/vnd.geogebra.file'], + 'ggt' => ['application/vnd.geogebra.tool'], + 'ghf' => ['application/vnd.groove-help'], + 'gif' => ['image/gif'], + 'gih' => ['image/x-gimp-gih'], + 'gim' => ['application/vnd.groove-identity-message'], + 'glade' => ['application/x-glade'], + 'gml' => ['application/gml+xml'], + 'gmo' => ['application/x-gettext-translation'], + 'gmx' => ['application/vnd.gmx'], + 'gnc' => ['application/x-gnucash'], + 'gnd' => ['application/gnunet-directory'], + 'gnucash' => ['application/x-gnucash'], + 'gnumeric' => ['application/x-gnumeric'], + 'gnuplot' => ['application/x-gnuplot'], + 'go' => ['text/x-go'], + 'gp' => ['application/x-gnuplot'], + 'gpg' => ['application/pgp', 'application/pgp-encrypted', 'application/pgp-keys', 'application/pgp-signature'], + 'gph' => ['application/vnd.flographit'], + 'gplt' => ['application/x-gnuplot'], + 'gpx' => ['application/gpx', 'application/gpx+xml', 'application/x-gpx', 'application/x-gpx+xml'], + 'gqf' => ['application/vnd.grafeq'], + 'gqs' => ['application/vnd.grafeq'], + 'gra' => ['application/x-graphite'], + 'gram' => ['application/srgs'], + 'gramps' => ['application/x-gramps-xml'], + 'gre' => ['application/vnd.geometry-explorer'], + 'grv' => ['application/vnd.groove-injector'], + 'grxml' => ['application/srgs+xml'], + 'gs' => ['text/x-genie'], + 'gsf' => ['application/x-font-ghostscript', 'application/x-font-type1'], + 'gsm' => ['audio/x-gsm'], + 'gtar' => ['application/x-gtar', 'application/x-tar'], + 'gtm' => ['application/vnd.groove-tool-message'], + 'gtw' => ['model/vnd.gtw'], + 'gv' => ['text/vnd.graphviz'], + 'gvp' => ['text/google-video-pointer', 'text/x-google-video-pointer'], + 'gxf' => ['application/gxf'], + 'gxt' => ['application/vnd.geonext'], + 'gz' => ['application/x-gzip', 'application/gzip'], + 'h' => ['text/x-c', 'text/x-chdr'], + 'h++' => ['text/x-c++hdr'], + 'h261' => ['video/h261'], + 'h263' => ['video/h263'], + 'h264' => ['video/h264'], + 'h4' => ['application/x-hdf'], + 'h5' => ['application/x-hdf'], + 'hal' => ['application/vnd.hal+xml'], + 'hbci' => ['application/vnd.hbci'], + 'hdf' => ['application/x-hdf'], + 'hdf4' => ['application/x-hdf'], + 'hdf5' => ['application/x-hdf'], + 'heic' => ['image/heic', 'image/heic-sequence', 'image/heif', 'image/heif-sequence'], + 'heif' => ['image/heic', 'image/heic-sequence', 'image/heif', 'image/heif-sequence'], + 'hfe' => ['application/x-hfe-file', 'application/x-hfe-floppy-image'], + 'hh' => ['text/x-c', 'text/x-c++hdr'], + 'hlp' => ['application/winhlp', 'zz-application/zz-winassoc-hlp'], + 'hp' => ['text/x-c++hdr'], + 'hpgl' => ['application/vnd.hp-hpgl'], + 'hpid' => ['application/vnd.hp-hpid'], + 'hpp' => ['text/x-c++hdr'], + 'hps' => ['application/vnd.hp-hps'], + 'hqx' => ['application/stuffit', 'application/mac-binhex40'], + 'hs' => ['text/x-haskell'], + 'htke' => ['application/vnd.kenameaapp'], + 'htm' => ['text/html'], + 'html' => ['text/html'], + 'hvd' => ['application/vnd.yamaha.hv-dic'], + 'hvp' => ['application/vnd.yamaha.hv-voice'], + 'hvs' => ['application/vnd.yamaha.hv-script'], + 'hwp' => ['application/vnd.haansoft-hwp', 'application/x-hwp'], + 'hwt' => ['application/vnd.haansoft-hwt', 'application/x-hwt'], + 'hxx' => ['text/x-c++hdr'], + 'i2g' => ['application/vnd.intergeo'], + 'ica' => ['application/x-ica'], + 'icb' => ['image/x-icb', 'image/x-tga'], + 'icc' => ['application/vnd.iccprofile'], + 'ice' => ['x-conference/x-cooltalk'], + 'icm' => ['application/vnd.iccprofile'], + 'icns' => ['image/x-icns'], + 'ico' => ['application/ico', 'image/ico', 'image/icon', 'image/vnd.microsoft.icon', 'image/x-ico', 'image/x-icon', 'text/ico'], + 'ics' => ['application/ics', 'text/calendar', 'text/x-vcalendar'], + 'idl' => ['text/x-idl'], + 'ief' => ['image/ief'], + 'ifb' => ['text/calendar'], + 'iff' => ['image/x-iff', 'image/x-ilbm'], + 'ifm' => ['application/vnd.shana.informed.formdata'], + 'iges' => ['model/iges'], + 'igl' => ['application/vnd.igloader'], + 'igm' => ['application/vnd.insors.igm'], + 'igs' => ['model/iges'], + 'igx' => ['application/vnd.micrografx.igx'], + 'iif' => ['application/vnd.shana.informed.interchange'], + 'ilbm' => ['image/x-iff', 'image/x-ilbm'], + 'ime' => ['audio/imelody', 'audio/x-imelody', 'text/x-imelody'], + 'img' => ['application/x-raw-disk-image'], + 'img.xz' => ['application/x-raw-disk-image-xz-compressed'], + 'imp' => ['application/vnd.accpac.simply.imp'], + 'ims' => ['application/vnd.ms-ims'], + 'imy' => ['audio/imelody', 'audio/x-imelody', 'text/x-imelody'], + 'in' => ['text/plain'], + 'ink' => ['application/inkml+xml'], + 'inkml' => ['application/inkml+xml'], + 'ins' => ['application/x-tex', 'text/x-tex'], + 'install' => ['application/x-install-instructions'], + 'iota' => ['application/vnd.astraea-software.iota'], + 'ipfix' => ['application/ipfix'], + 'ipk' => ['application/vnd.shana.informed.package'], + 'iptables' => ['text/x-iptables'], + 'ipynb' => ['application/x-ipynb+json'], + 'irm' => ['application/vnd.ibm.rights-management'], + 'irp' => ['application/vnd.irepository.package+xml'], + 'iso' => ['application/x-cd-image', 'application/x-gamecube-iso-image', 'application/x-gamecube-rom', 'application/x-iso9660-image', 'application/x-saturn-rom', 'application/x-sega-cd-rom', 'application/x-wbfs', 'application/x-wia', 'application/x-wii-iso-image', 'application/x-wii-rom'], + 'iso9660' => ['application/x-cd-image', 'application/x-iso9660-image'], + 'it' => ['audio/x-it'], + 'it87' => ['application/x-it87'], + 'itp' => ['application/vnd.shana.informed.formtemplate'], + 'ivp' => ['application/vnd.immervision-ivp'], + 'ivu' => ['application/vnd.immervision-ivu'], + 'j2c' => ['image/x-jp2-codestream'], + 'j2k' => ['image/x-jp2-codestream'], + 'jad' => ['text/vnd.sun.j2me.app-descriptor'], + 'jam' => ['application/vnd.jam'], + 'jar' => ['application/x-java-archive', 'application/java-archive', 'application/x-jar'], + 'java' => ['text/x-java', 'text/x-java-source'], + 'jceks' => ['application/x-java-jce-keystore'], + 'jisp' => ['application/vnd.jisp'], + 'jks' => ['application/x-java-keystore'], + 'jlt' => ['application/vnd.hp-jlyt'], + 'jng' => ['image/x-jng'], + 'jnlp' => ['application/x-java-jnlp-file'], + 'joda' => ['application/vnd.joost.joda-archive'], + 'jp2' => ['image/jp2', 'image/jpeg2000', 'image/jpeg2000-image', 'image/x-jpeg2000-image'], + 'jpc' => ['image/x-jp2-codestream'], + 'jpe' => ['image/jpeg', 'image/pjpeg'], + 'jpeg' => ['image/jpeg', 'image/pjpeg'], + 'jpf' => ['image/jpx'], + 'jpg' => ['image/jpeg', 'image/pjpeg'], + 'jpg2' => ['image/jp2', 'image/jpeg2000', 'image/jpeg2000-image', 'image/x-jpeg2000-image'], + 'jpgm' => ['image/jpm', 'video/jpm'], + 'jpgv' => ['video/jpeg'], + 'jpm' => ['image/jpm', 'video/jpm'], + 'jpr' => ['application/x-jbuilder-project'], + 'jpx' => ['application/x-jbuilder-project', 'image/jpx'], + 'jrd' => ['application/jrd+json'], + 'js' => ['text/javascript', 'application/javascript', 'application/x-javascript'], + 'jsm' => ['application/javascript', 'application/x-javascript', 'text/javascript'], + 'json' => ['application/json'], + 'json-patch' => ['application/json-patch+json'], + 'jsonld' => ['application/ld+json'], + 'jsonml' => ['application/jsonml+json'], + 'k25' => ['image/x-kodak-k25'], + 'k7' => ['application/x-thomson-cassette'], + 'kar' => ['audio/midi', 'audio/x-midi'], + 'karbon' => ['application/vnd.kde.karbon', 'application/x-karbon'], + 'kdc' => ['image/x-kodak-kdc'], + 'kdelnk' => ['application/x-desktop', 'application/x-gnome-app-info'], + 'kexi' => ['application/x-kexiproject-sqlite', 'application/x-kexiproject-sqlite2', 'application/x-kexiproject-sqlite3', 'application/x-vnd.kde.kexi'], + 'kexic' => ['application/x-kexi-connectiondata'], + 'kexis' => ['application/x-kexiproject-shortcut'], + 'key' => ['application/vnd.apple.keynote', 'application/x-iwork-keynote-sffkey'], + 'kfo' => ['application/vnd.kde.kformula', 'application/x-kformula'], + 'kia' => ['application/vnd.kidspiration'], + 'kil' => ['application/x-killustrator'], + 'kino' => ['application/smil', 'application/smil+xml'], + 'kml' => ['application/vnd.google-earth.kml+xml'], + 'kmz' => ['application/vnd.google-earth.kmz'], + 'kne' => ['application/vnd.kinar'], + 'knp' => ['application/vnd.kinar'], + 'kon' => ['application/vnd.kde.kontour', 'application/x-kontour'], + 'kpm' => ['application/x-kpovmodeler'], + 'kpr' => ['application/vnd.kde.kpresenter', 'application/x-kpresenter'], + 'kpt' => ['application/vnd.kde.kpresenter', 'application/x-kpresenter'], + 'kpxx' => ['application/vnd.ds-keypoint'], + 'kra' => ['application/x-krita'], + 'ks' => ['application/x-java-keystore'], + 'ksp' => ['application/vnd.kde.kspread', 'application/x-kspread'], + 'ktr' => ['application/vnd.kahootz'], + 'ktx' => ['image/ktx'], + 'ktz' => ['application/vnd.kahootz'], + 'kud' => ['application/x-kugar'], + 'kwd' => ['application/vnd.kde.kword', 'application/x-kword'], + 'kwt' => ['application/vnd.kde.kword', 'application/x-kword'], + 'la' => ['application/x-shared-library-la'], + 'lasxml' => ['application/vnd.las.las+xml'], + 'latex' => ['application/x-latex', 'application/x-tex', 'text/x-tex'], + 'lbd' => ['application/vnd.llamagraphics.life-balance.desktop'], + 'lbe' => ['application/vnd.llamagraphics.life-balance.exchange+xml'], + 'lbm' => ['image/x-iff', 'image/x-ilbm'], + 'ldif' => ['text/x-ldif'], + 'les' => ['application/vnd.hhe.lesson-player'], + 'lha' => ['application/x-lha', 'application/x-lzh-compressed'], + 'lhs' => ['text/x-literate-haskell'], + 'lhz' => ['application/x-lhz'], + 'link66' => ['application/vnd.route66.link66+xml'], + 'list' => ['text/plain'], + 'list3820' => ['application/vnd.ibm.modcap'], + 'listafp' => ['application/vnd.ibm.modcap'], + 'lnk' => ['application/x-ms-shortcut'], + 'lnx' => ['application/x-atari-lynx-rom'], + 'loas' => ['audio/usac'], + 'log' => ['text/plain', 'text/x-log'], + 'lostxml' => ['application/lost+xml'], + 'lrf' => ['application/octet-stream'], + 'lrm' => ['application/vnd.ms-lrm'], + 'lrv' => ['video/mp4', 'video/mp4v-es', 'video/x-m4v'], + 'lrz' => ['application/x-lrzip'], + 'ltf' => ['application/vnd.frogans.ltf'], + 'ltx' => ['application/x-tex', 'text/x-tex'], + 'lua' => ['text/x-lua'], + 'lvp' => ['audio/vnd.lucent.voice'], + 'lwo' => ['image/x-lwo'], + 'lwob' => ['image/x-lwo'], + 'lwp' => ['application/vnd.lotus-wordpro'], + 'lws' => ['image/x-lws'], + 'ly' => ['text/x-lilypond'], + 'lyx' => ['application/x-lyx', 'text/x-lyx'], + 'lz' => ['application/x-lzip'], + 'lz4' => ['application/x-lz4'], + 'lzh' => ['application/x-lha', 'application/x-lzh-compressed'], + 'lzma' => ['application/x-lzma'], + 'lzo' => ['application/x-lzop'], + 'm' => ['text/x-matlab', 'text/x-objcsrc', 'text/x-octave'], + 'm13' => ['application/x-msmediaview'], + 'm14' => ['application/x-msmediaview'], + 'm15' => ['audio/x-mod'], + 'm1u' => ['video/vnd.mpegurl', 'video/x-mpegurl'], + 'm1v' => ['video/mpeg'], + 'm21' => ['application/mp21'], + 'm2a' => ['audio/mpeg'], + 'm2t' => ['video/mp2t'], + 'm2ts' => ['video/mp2t'], + 'm2v' => ['video/mpeg'], + 'm3a' => ['audio/mpeg'], + 'm3u' => ['audio/x-mpegurl', 'application/m3u', 'application/vnd.apple.mpegurl', 'audio/m3u', 'audio/mpegurl', 'audio/x-m3u', 'audio/x-mp3-playlist'], + 'm3u8' => ['application/m3u', 'application/vnd.apple.mpegurl', 'audio/m3u', 'audio/mpegurl', 'audio/x-m3u', 'audio/x-mp3-playlist', 'audio/x-mpegurl'], + 'm4' => ['application/x-m4'], + 'm4a' => ['audio/mp4', 'audio/m4a', 'audio/x-m4a'], + 'm4b' => ['audio/x-m4b'], + 'm4r' => ['audio/x-m4r'], + 'm4u' => ['video/vnd.mpegurl', 'video/x-mpegurl'], + 'm4v' => ['video/mp4', 'video/mp4v-es', 'video/x-m4v'], + 'm7' => ['application/x-thomson-cartridge-memo7'], + 'ma' => ['application/mathematica'], + 'mab' => ['application/x-markaby'], + 'mads' => ['application/mads+xml'], + 'mag' => ['application/vnd.ecowin.chart'], + 'mak' => ['text/x-makefile'], + 'maker' => ['application/vnd.framemaker'], + 'man' => ['application/x-troff-man', 'text/troff'], + 'manifest' => ['text/cache-manifest'], + 'mar' => ['application/octet-stream'], + 'markdown' => ['text/markdown', 'text/x-markdown'], + 'mathml' => ['application/mathml+xml'], + 'mb' => ['application/mathematica'], + 'mbk' => ['application/vnd.mobius.mbk'], + 'mbox' => ['application/mbox'], + 'mc1' => ['application/vnd.medcalcdata'], + 'mcd' => ['application/vnd.mcd'], + 'mcurl' => ['text/vnd.curl.mcurl'], + 'md' => ['text/markdown', 'text/x-markdown'], + 'mdb' => ['application/x-msaccess', 'application/mdb', 'application/msaccess', 'application/vnd.ms-access', 'application/vnd.msaccess', 'application/x-mdb', 'zz-application/zz-winassoc-mdb'], + 'mdi' => ['image/vnd.ms-modi'], + 'mdx' => ['application/x-genesis-32x-rom'], + 'me' => ['text/troff', 'text/x-troff-me'], + 'med' => ['audio/x-mod'], + 'mesh' => ['model/mesh'], + 'meta4' => ['application/metalink4+xml'], + 'metalink' => ['application/metalink+xml'], + 'mets' => ['application/mets+xml'], + 'mfm' => ['application/vnd.mfmp'], + 'mft' => ['application/rpki-manifest'], + 'mgp' => ['application/vnd.osgeo.mapguide.package', 'application/x-magicpoint'], + 'mgz' => ['application/vnd.proteus.magazine'], + 'mht' => ['application/x-mimearchive'], + 'mhtml' => ['application/x-mimearchive'], + 'mid' => ['audio/midi', 'audio/x-midi'], + 'midi' => ['audio/midi', 'audio/x-midi'], + 'mie' => ['application/x-mie'], + 'mif' => ['application/vnd.mif', 'application/x-mif'], + 'mime' => ['message/rfc822'], + 'minipsf' => ['audio/x-minipsf'], + 'mj2' => ['video/mj2'], + 'mjp2' => ['video/mj2'], + 'mjpeg' => ['video/x-mjpeg'], + 'mjpg' => ['video/x-mjpeg'], + 'mjs' => ['application/javascript', 'application/x-javascript', 'text/javascript'], + 'mk' => ['text/x-makefile'], + 'mk3d' => ['video/x-matroska', 'video/x-matroska-3d'], + 'mka' => ['audio/x-matroska'], + 'mkd' => ['text/markdown', 'text/x-markdown'], + 'mks' => ['video/x-matroska'], + 'mkv' => ['video/x-matroska'], + 'ml' => ['text/x-ocaml'], + 'mli' => ['text/x-ocaml'], + 'mlp' => ['application/vnd.dolby.mlp'], + 'mm' => ['text/x-troff-mm'], + 'mmd' => ['application/vnd.chipnuts.karaoke-mmd'], + 'mmf' => ['application/vnd.smaf', 'application/x-smaf'], + 'mml' => ['application/mathml+xml', 'text/mathml'], + 'mmr' => ['image/vnd.fujixerox.edmics-mmr'], + 'mng' => ['video/x-mng'], + 'mny' => ['application/x-msmoney'], + 'mo' => ['application/x-gettext-translation', 'text/x-modelica'], + 'mo3' => ['audio/x-mo3'], + 'mobi' => ['application/x-mobipocket-ebook'], + 'moc' => ['text/x-moc'], + 'mod' => ['audio/x-mod'], + 'mods' => ['application/mods+xml'], + 'mof' => ['text/x-mof'], + 'moov' => ['video/quicktime'], + 'mount' => ['text/x-systemd-unit'], + 'mov' => ['video/quicktime'], + 'movie' => ['video/x-sgi-movie'], + 'mp+' => ['audio/x-musepack'], + 'mp2' => ['audio/mp2', 'audio/mpeg', 'audio/x-mp2', 'video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2'], + 'mp21' => ['application/mp21'], + 'mp2a' => ['audio/mpeg'], + 'mp3' => ['audio/mpeg', 'audio/mp3', 'audio/x-mp3', 'audio/x-mpeg', 'audio/x-mpg'], + 'mp4' => ['video/mp4', 'video/mp4v-es', 'video/x-m4v'], + 'mp4a' => ['audio/mp4'], + 'mp4s' => ['application/mp4'], + 'mp4v' => ['video/mp4'], + 'mpc' => ['application/vnd.mophun.certificate', 'audio/x-musepack'], + 'mpe' => ['video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2'], + 'mpeg' => ['video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2'], + 'mpg' => ['video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2'], + 'mpg4' => ['video/mp4'], + 'mpga' => ['audio/mp3', 'audio/mpeg', 'audio/x-mp3', 'audio/x-mpeg', 'audio/x-mpg'], + 'mpkg' => ['application/vnd.apple.installer+xml'], + 'mpl' => ['video/mp2t'], + 'mpls' => ['video/mp2t'], + 'mpm' => ['application/vnd.blueice.multipass'], + 'mpn' => ['application/vnd.mophun.application'], + 'mpp' => ['application/vnd.ms-project', 'audio/x-musepack'], + 'mpt' => ['application/vnd.ms-project'], + 'mpy' => ['application/vnd.ibm.minipay'], + 'mqy' => ['application/vnd.mobius.mqy'], + 'mrc' => ['application/marc'], + 'mrcx' => ['application/marcxml+xml'], + 'mrl' => ['text/x-mrml'], + 'mrml' => ['text/x-mrml'], + 'mrw' => ['image/x-minolta-mrw'], + 'ms' => ['text/troff', 'text/x-troff-ms'], + 'mscml' => ['application/mediaservercontrol+xml'], + 'mseed' => ['application/vnd.fdsn.mseed'], + 'mseq' => ['application/vnd.mseq'], + 'msf' => ['application/vnd.epson.msf'], + 'msh' => ['model/mesh'], + 'msi' => ['application/x-msdownload', 'application/x-msi'], + 'msl' => ['application/vnd.mobius.msl'], + 'msod' => ['image/x-msod'], + 'msty' => ['application/vnd.muvee.style'], + 'msx' => ['application/x-msx-rom'], + 'mtm' => ['audio/x-mod'], + 'mts' => ['model/vnd.mts', 'video/mp2t'], + 'mup' => ['text/x-mup'], + 'mus' => ['application/vnd.musician'], + 'musicxml' => ['application/vnd.recordare.musicxml+xml'], + 'mvb' => ['application/x-msmediaview'], + 'mwf' => ['application/vnd.mfer'], + 'mxf' => ['application/mxf'], + 'mxl' => ['application/vnd.recordare.musicxml'], + 'mxml' => ['application/xv+xml'], + 'mxs' => ['application/vnd.triscape.mxs'], + 'mxu' => ['video/vnd.mpegurl', 'video/x-mpegurl'], + 'n-gage' => ['application/vnd.nokia.n-gage.symbian.install'], + 'n3' => ['text/n3'], + 'n64' => ['application/x-n64-rom'], + 'nb' => ['application/mathematica', 'application/x-mathematica'], + 'nbp' => ['application/vnd.wolfram.player'], + 'nc' => ['application/x-netcdf'], + 'ncx' => ['application/x-dtbncx+xml'], + 'nds' => ['application/x-nintendo-ds-rom'], + 'nef' => ['image/x-nikon-nef'], + 'nes' => ['application/x-nes-rom'], + 'nez' => ['application/x-nes-rom'], + 'nfo' => ['text/x-nfo'], + 'ngc' => ['application/x-neo-geo-pocket-color-rom'], + 'ngdat' => ['application/vnd.nokia.n-gage.data'], + 'ngp' => ['application/x-neo-geo-pocket-rom'], + 'nitf' => ['application/vnd.nitf'], + 'nlu' => ['application/vnd.neurolanguage.nlu'], + 'nml' => ['application/vnd.enliven'], + 'nnd' => ['application/vnd.noblenet-directory'], + 'nns' => ['application/vnd.noblenet-sealer'], + 'nnw' => ['application/vnd.noblenet-web'], + 'not' => ['text/x-mup'], + 'npx' => ['image/vnd.net-fpx'], + 'nsc' => ['application/x-conference', 'application/x-netshow-channel'], + 'nsf' => ['application/vnd.lotus-notes'], + 'nsv' => ['video/x-nsv'], + 'ntf' => ['application/vnd.nitf'], + 'nzb' => ['application/x-nzb'], + 'o' => ['application/x-object'], + 'oa2' => ['application/vnd.fujitsu.oasys2'], + 'oa3' => ['application/vnd.fujitsu.oasys3'], + 'oas' => ['application/vnd.fujitsu.oasys'], + 'obd' => ['application/x-msbinder'], + 'obj' => ['application/x-tgif'], + 'ocl' => ['text/x-ocl'], + 'oda' => ['application/oda'], + 'odb' => ['application/vnd.oasis.opendocument.database', 'application/vnd.sun.xml.base'], + 'odc' => ['application/vnd.oasis.opendocument.chart'], + 'odf' => ['application/vnd.oasis.opendocument.formula'], + 'odft' => ['application/vnd.oasis.opendocument.formula-template'], + 'odg' => ['application/vnd.oasis.opendocument.graphics'], + 'odi' => ['application/vnd.oasis.opendocument.image'], + 'odm' => ['application/vnd.oasis.opendocument.text-master'], + 'odp' => ['application/vnd.oasis.opendocument.presentation'], + 'ods' => ['application/vnd.oasis.opendocument.spreadsheet'], + 'odt' => ['application/vnd.oasis.opendocument.text'], + 'oga' => ['audio/ogg', 'audio/vorbis', 'audio/x-flac+ogg', 'audio/x-ogg', 'audio/x-oggflac', 'audio/x-speex+ogg', 'audio/x-vorbis', 'audio/x-vorbis+ogg'], + 'ogg' => ['audio/ogg', 'audio/vorbis', 'audio/x-flac+ogg', 'audio/x-ogg', 'audio/x-oggflac', 'audio/x-speex+ogg', 'audio/x-vorbis', 'audio/x-vorbis+ogg', 'video/ogg', 'video/x-ogg', 'video/x-theora', 'video/x-theora+ogg'], + 'ogm' => ['video/x-ogm', 'video/x-ogm+ogg'], + 'ogv' => ['video/ogg', 'video/x-ogg'], + 'ogx' => ['application/ogg', 'application/x-ogg'], + 'old' => ['application/x-trash'], + 'oleo' => ['application/x-oleo'], + 'omdoc' => ['application/omdoc+xml'], + 'onepkg' => ['application/onenote'], + 'onetmp' => ['application/onenote'], + 'onetoc' => ['application/onenote'], + 'onetoc2' => ['application/onenote'], + 'ooc' => ['text/x-ooc'], + 'opf' => ['application/oebps-package+xml'], + 'opml' => ['text/x-opml', 'text/x-opml+xml'], + 'oprc' => ['application/vnd.palm', 'application/x-palm-database'], + 'opus' => ['audio/ogg', 'audio/x-ogg', 'audio/x-opus+ogg'], + 'ora' => ['image/openraster'], + 'orf' => ['image/x-olympus-orf'], + 'org' => ['application/vnd.lotus-organizer'], + 'osf' => ['application/vnd.yamaha.openscoreformat'], + 'osfpvg' => ['application/vnd.yamaha.openscoreformat.osfpvg+xml'], + 'otc' => ['application/vnd.oasis.opendocument.chart-template'], + 'otf' => ['application/vnd.oasis.opendocument.formula-template', 'application/x-font-otf', 'font/otf'], + 'otg' => ['application/vnd.oasis.opendocument.graphics-template'], + 'oth' => ['application/vnd.oasis.opendocument.text-web'], + 'oti' => ['application/vnd.oasis.opendocument.image-template'], + 'otp' => ['application/vnd.oasis.opendocument.presentation-template'], + 'ots' => ['application/vnd.oasis.opendocument.spreadsheet-template'], + 'ott' => ['application/vnd.oasis.opendocument.text-template'], + 'owl' => ['application/rdf+xml', 'text/rdf'], + 'owx' => ['application/owl+xml'], + 'oxps' => ['application/oxps', 'application/vnd.ms-xpsdocument', 'application/xps'], + 'oxt' => ['application/vnd.openofficeorg.extension'], + 'p' => ['text/x-pascal'], + 'p10' => ['application/pkcs10'], + 'p12' => ['application/pkcs12', 'application/x-pkcs12'], + 'p65' => ['application/x-pagemaker'], + 'p7b' => ['application/x-pkcs7-certificates'], + 'p7c' => ['application/pkcs7-mime'], + 'p7m' => ['application/pkcs7-mime'], + 'p7r' => ['application/x-pkcs7-certreqresp'], + 'p7s' => ['application/pkcs7-signature'], + 'p8' => ['application/pkcs8'], + 'p8e' => ['application/pkcs8-encrypted'], + 'pack' => ['application/x-java-pack200'], + 'pak' => ['application/x-pak'], + 'par2' => ['application/x-par2'], + 'part' => ['application/x-partial-download'], + 'pas' => ['text/x-pascal'], + 'pat' => ['image/x-gimp-pat'], + 'patch' => ['text/x-diff', 'text/x-patch'], + 'path' => ['text/x-systemd-unit'], + 'paw' => ['application/vnd.pawaafile'], + 'pbd' => ['application/vnd.powerbuilder6'], + 'pbm' => ['image/x-portable-bitmap'], + 'pcap' => ['application/pcap', 'application/vnd.tcpdump.pcap', 'application/x-pcap'], + 'pcd' => ['image/x-photo-cd'], + 'pce' => ['application/x-pc-engine-rom'], + 'pcf' => ['application/x-cisco-vpn-settings', 'application/x-font-pcf'], + 'pcf.Z' => ['application/x-font-pcf'], + 'pcf.gz' => ['application/x-font-pcf'], + 'pcl' => ['application/vnd.hp-pcl'], + 'pclxl' => ['application/vnd.hp-pclxl'], + 'pct' => ['image/x-pict'], + 'pcurl' => ['application/vnd.curl.pcurl'], + 'pcx' => ['image/vnd.zbrush.pcx', 'image/x-pcx'], + 'pdb' => ['application/vnd.palm', 'application/x-aportisdoc', 'application/x-palm-database'], + 'pdc' => ['application/x-aportisdoc'], + 'pdf' => ['application/pdf', 'application/acrobat', 'application/nappdf', 'application/x-pdf', 'image/pdf'], + 'pdf.bz2' => ['application/x-bzpdf'], + 'pdf.gz' => ['application/x-gzpdf'], + 'pdf.lz' => ['application/x-lzpdf'], + 'pdf.xz' => ['application/x-xzpdf'], + 'pef' => ['image/x-pentax-pef'], + 'pem' => ['application/x-x509-ca-cert'], + 'perl' => ['application/x-perl', 'text/x-perl'], + 'pfa' => ['application/x-font-type1'], + 'pfb' => ['application/x-font-type1'], + 'pfm' => ['application/x-font-type1'], + 'pfr' => ['application/font-tdpfr'], + 'pfx' => ['application/pkcs12', 'application/x-pkcs12'], + 'pgm' => ['image/x-portable-graymap'], + 'pgn' => ['application/vnd.chess-pgn', 'application/x-chess-pgn'], + 'pgp' => ['application/pgp', 'application/pgp-encrypted', 'application/pgp-keys', 'application/pgp-signature'], + 'php' => ['application/x-php'], + 'php3' => ['application/x-php'], + 'php4' => ['application/x-php'], + 'php5' => ['application/x-php'], + 'phps' => ['application/x-php'], + 'pic' => ['image/x-pict'], + 'pict' => ['image/x-pict'], + 'pict1' => ['image/x-pict'], + 'pict2' => ['image/x-pict'], + 'pk' => ['application/x-tex-pk'], + 'pkg' => ['application/octet-stream', 'application/x-xar'], + 'pki' => ['application/pkixcmp'], + 'pkipath' => ['application/pkix-pkipath'], + 'pkr' => ['application/pgp-keys'], + 'pl' => ['application/x-perl', 'text/x-perl'], + 'pla' => ['audio/x-iriver-pla'], + 'plb' => ['application/vnd.3gpp.pic-bw-large'], + 'plc' => ['application/vnd.mobius.plc'], + 'plf' => ['application/vnd.pocketlearn'], + 'pln' => ['application/x-planperfect'], + 'pls' => ['application/pls', 'application/pls+xml', 'audio/scpls', 'audio/x-scpls'], + 'pm' => ['application/x-pagemaker', 'application/x-perl', 'text/x-perl'], + 'pm6' => ['application/x-pagemaker'], + 'pmd' => ['application/x-pagemaker'], + 'pml' => ['application/vnd.ctc-posml'], + 'png' => ['image/png'], + 'pnm' => ['image/x-portable-anymap'], + 'pntg' => ['image/x-macpaint'], + 'po' => ['application/x-gettext', 'text/x-gettext-translation', 'text/x-po'], + 'pod' => ['application/x-perl', 'text/x-perl'], + 'por' => ['application/x-spss-por'], + 'portpkg' => ['application/vnd.macports.portpkg'], + 'pot' => ['application/mspowerpoint', 'application/powerpoint', 'application/vnd.ms-powerpoint', 'application/x-mspowerpoint', 'text/x-gettext-translation-template', 'text/x-pot'], + 'potm' => ['application/vnd.ms-powerpoint.template.macroenabled.12'], + 'potx' => ['application/vnd.openxmlformats-officedocument.presentationml.template'], + 'ppam' => ['application/vnd.ms-powerpoint.addin.macroenabled.12'], + 'ppd' => ['application/vnd.cups-ppd'], + 'ppm' => ['image/x-portable-pixmap'], + 'pps' => ['application/mspowerpoint', 'application/powerpoint', 'application/vnd.ms-powerpoint', 'application/x-mspowerpoint'], + 'ppsm' => ['application/vnd.ms-powerpoint.slideshow.macroenabled.12'], + 'ppsx' => ['application/vnd.openxmlformats-officedocument.presentationml.slideshow'], + 'ppt' => ['application/vnd.ms-powerpoint', 'application/mspowerpoint', 'application/powerpoint', 'application/x-mspowerpoint'], + 'pptm' => ['application/vnd.ms-powerpoint.presentation.macroenabled.12'], + 'pptx' => ['application/vnd.openxmlformats-officedocument.presentationml.presentation'], + 'ppz' => ['application/mspowerpoint', 'application/powerpoint', 'application/vnd.ms-powerpoint', 'application/x-mspowerpoint'], + 'pqa' => ['application/vnd.palm', 'application/x-palm-database'], + 'prc' => ['application/vnd.palm', 'application/x-mobipocket-ebook', 'application/x-palm-database'], + 'pre' => ['application/vnd.lotus-freelance'], + 'prf' => ['application/pics-rules'], + 'ps' => ['application/postscript'], + 'ps.bz2' => ['application/x-bzpostscript'], + 'ps.gz' => ['application/x-gzpostscript'], + 'psb' => ['application/vnd.3gpp.pic-bw-small'], + 'psd' => ['application/photoshop', 'application/x-photoshop', 'image/photoshop', 'image/psd', 'image/vnd.adobe.photoshop', 'image/x-photoshop', 'image/x-psd'], + 'psf' => ['application/x-font-linux-psf', 'audio/x-psf'], + 'psf.gz' => ['application/x-gz-font-linux-psf'], + 'psflib' => ['audio/x-psflib'], + 'psid' => ['audio/prs.sid'], + 'pskcxml' => ['application/pskc+xml'], + 'psw' => ['application/x-pocket-word'], + 'ptid' => ['application/vnd.pvi.ptid1'], + 'pub' => ['application/vnd.ms-publisher', 'application/x-mspublisher'], + 'pvb' => ['application/vnd.3gpp.pic-bw-var'], + 'pw' => ['application/x-pw'], + 'pwn' => ['application/vnd.3m.post-it-notes'], + 'py' => ['text/x-python', 'text/x-python3'], + 'py3' => ['text/x-python3'], + 'py3x' => ['text/x-python3'], + 'pya' => ['audio/vnd.ms-playready.media.pya'], + 'pyc' => ['application/x-python-bytecode'], + 'pyo' => ['application/x-python-bytecode'], + 'pyv' => ['video/vnd.ms-playready.media.pyv'], + 'pyx' => ['text/x-python'], + 'qam' => ['application/vnd.epson.quickanime'], + 'qbo' => ['application/vnd.intu.qbo'], + 'qd' => ['application/x-fd-file', 'application/x-raw-floppy-disk-image'], + 'qfx' => ['application/vnd.intu.qfx'], + 'qif' => ['application/x-qw', 'image/x-quicktime'], + 'qml' => ['text/x-qml'], + 'qmlproject' => ['text/x-qml'], + 'qmltypes' => ['text/x-qml'], + 'qp' => ['application/x-qpress'], + 'qps' => ['application/vnd.publishare-delta-tree'], + 'qt' => ['video/quicktime'], + 'qti' => ['application/x-qtiplot'], + 'qti.gz' => ['application/x-qtiplot'], + 'qtif' => ['image/x-quicktime'], + 'qtl' => ['application/x-quicktime-media-link', 'application/x-quicktimeplayer'], + 'qtvr' => ['video/quicktime'], + 'qwd' => ['application/vnd.quark.quarkxpress'], + 'qwt' => ['application/vnd.quark.quarkxpress'], + 'qxb' => ['application/vnd.quark.quarkxpress'], + 'qxd' => ['application/vnd.quark.quarkxpress'], + 'qxl' => ['application/vnd.quark.quarkxpress'], + 'qxt' => ['application/vnd.quark.quarkxpress'], + 'ra' => ['audio/vnd.m-realaudio', 'audio/vnd.rn-realaudio', 'audio/x-pn-realaudio'], + 'raf' => ['image/x-fuji-raf'], + 'ram' => ['application/ram', 'audio/x-pn-realaudio'], + 'raml' => ['application/raml+yaml'], + 'rar' => ['application/x-rar-compressed', 'application/vnd.rar', 'application/x-rar'], + 'ras' => ['image/x-cmu-raster'], + 'raw' => ['image/x-panasonic-raw', 'image/x-panasonic-rw'], + 'raw-disk-image' => ['application/x-raw-disk-image'], + 'raw-disk-image.xz' => ['application/x-raw-disk-image-xz-compressed'], + 'rax' => ['audio/vnd.m-realaudio', 'audio/vnd.rn-realaudio', 'audio/x-pn-realaudio'], + 'rb' => ['application/x-ruby'], + 'rcprofile' => ['application/vnd.ipunplugged.rcprofile'], + 'rdf' => ['application/rdf+xml', 'text/rdf'], + 'rdfs' => ['application/rdf+xml', 'text/rdf'], + 'rdz' => ['application/vnd.data-vision.rdz'], + 'reg' => ['text/x-ms-regedit'], + 'rej' => ['application/x-reject', 'text/x-reject'], + 'rep' => ['application/vnd.businessobjects'], + 'res' => ['application/x-dtbresource+xml'], + 'rgb' => ['image/x-rgb'], + 'rif' => ['application/reginfo+xml'], + 'rip' => ['audio/vnd.rip'], + 'ris' => ['application/x-research-info-systems'], + 'rl' => ['application/resource-lists+xml'], + 'rlc' => ['image/vnd.fujixerox.edmics-rlc'], + 'rld' => ['application/resource-lists-diff+xml'], + 'rle' => ['image/rle'], + 'rm' => ['application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'], + 'rmi' => ['audio/midi'], + 'rmj' => ['application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'], + 'rmm' => ['application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'], + 'rmp' => ['audio/x-pn-realaudio-plugin'], + 'rms' => ['application/vnd.jcp.javame.midlet-rms', 'application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'], + 'rmvb' => ['application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'], + 'rmx' => ['application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'], + 'rnc' => ['application/relax-ng-compact-syntax', 'application/x-rnc'], + 'rng' => ['application/xml', 'text/xml'], + 'roa' => ['application/rpki-roa'], + 'roff' => ['application/x-troff', 'text/troff', 'text/x-troff'], + 'rp' => ['image/vnd.rn-realpix'], + 'rp9' => ['application/vnd.cloanto.rp9'], + 'rpm' => ['application/x-redhat-package-manager', 'application/x-rpm'], + 'rpss' => ['application/vnd.nokia.radio-presets'], + 'rpst' => ['application/vnd.nokia.radio-preset'], + 'rq' => ['application/sparql-query'], + 'rs' => ['application/rls-services+xml', 'text/rust'], + 'rsd' => ['application/rsd+xml'], + 'rss' => ['application/rss+xml', 'text/rss'], + 'rt' => ['text/vnd.rn-realtext'], + 'rtf' => ['application/rtf', 'text/rtf'], + 'rtx' => ['text/richtext'], + 'rv' => ['video/vnd.rn-realvideo', 'video/x-real-video'], + 'rvx' => ['video/vnd.rn-realvideo', 'video/x-real-video'], + 'rw2' => ['image/x-panasonic-raw2', 'image/x-panasonic-rw2'], + 's' => ['text/x-asm'], + 's3m' => ['audio/s3m', 'audio/x-s3m'], + 'saf' => ['application/vnd.yamaha.smaf-audio'], + 'sam' => ['application/x-amipro'], + 'sami' => ['application/x-sami'], + 'sap' => ['application/x-sap-file', 'application/x-thomson-sap-image'], + 'sass' => ['text/x-sass'], + 'sav' => ['application/x-spss-sav', 'application/x-spss-savefile'], + 'sbml' => ['application/sbml+xml'], + 'sc' => ['application/vnd.ibm.secure-container'], + 'scala' => ['text/x-scala'], + 'scd' => ['application/x-msschedule'], + 'scm' => ['application/vnd.lotus-screencam', 'text/x-scheme'], + 'scope' => ['text/x-systemd-unit'], + 'scq' => ['application/scvp-cv-request'], + 'scs' => ['application/scvp-cv-response'], + 'scss' => ['text/x-scss'], + 'scurl' => ['text/vnd.curl.scurl'], + 'sda' => ['application/vnd.stardivision.draw'], + 'sdc' => ['application/vnd.stardivision.calc'], + 'sdd' => ['application/vnd.stardivision.impress'], + 'sdkd' => ['application/vnd.solent.sdkm+xml'], + 'sdkm' => ['application/vnd.solent.sdkm+xml'], + 'sdp' => ['application/sdp', 'application/vnd.sdp', 'application/vnd.stardivision.impress', 'application/x-sdp'], + 'sds' => ['application/vnd.stardivision.chart'], + 'sdw' => ['application/vnd.stardivision.writer', 'application/vnd.stardivision.writer-global'], + 'see' => ['application/vnd.seemail'], + 'seed' => ['application/vnd.fdsn.seed'], + 'sema' => ['application/vnd.sema'], + 'semd' => ['application/vnd.semd'], + 'semf' => ['application/vnd.semf'], + 'ser' => ['application/java-serialized-object'], + 'service' => ['text/x-dbus-service', 'text/x-systemd-unit'], + 'setpay' => ['application/set-payment-initiation'], + 'setreg' => ['application/set-registration-initiation'], + 'sfc' => ['application/vnd.nintendo.snes.rom', 'application/x-snes-rom'], + 'sfd-hdstx' => ['application/vnd.hydrostatix.sof-data'], + 'sfs' => ['application/vnd.spotfire.sfs'], + 'sfv' => ['text/x-sfv'], + 'sg' => ['application/x-sg1000-rom'], + 'sgb' => ['application/x-gameboy-rom'], + 'sgf' => ['application/x-go-sgf'], + 'sgi' => ['image/sgi', 'image/x-sgi'], + 'sgl' => ['application/vnd.stardivision.writer', 'application/vnd.stardivision.writer-global'], + 'sgm' => ['text/sgml'], + 'sgml' => ['text/sgml'], + 'sh' => ['application/x-sh', 'application/x-shellscript', 'text/x-sh'], + 'shape' => ['application/x-dia-shape'], + 'shar' => ['application/x-shar'], + 'shf' => ['application/shf+xml'], + 'shn' => ['application/x-shorten', 'audio/x-shorten'], + 'siag' => ['application/x-siag'], + 'sid' => ['audio/prs.sid', 'image/x-mrsid-image'], + 'sig' => ['application/pgp-signature'], + 'sik' => ['application/x-trash'], + 'sil' => ['audio/silk'], + 'silo' => ['model/mesh'], + 'sis' => ['application/vnd.symbian.install'], + 'sisx' => ['application/vnd.symbian.install', 'x-epoc/x-sisx-app'], + 'sit' => ['application/x-stuffit', 'application/stuffit', 'application/x-sit'], + 'sitx' => ['application/x-stuffitx'], + 'siv' => ['application/sieve'], + 'sk' => ['image/x-skencil'], + 'sk1' => ['image/x-skencil'], + 'skd' => ['application/vnd.koan'], + 'skm' => ['application/vnd.koan'], + 'skp' => ['application/vnd.koan'], + 'skr' => ['application/pgp-keys'], + 'skt' => ['application/vnd.koan'], + 'sldm' => ['application/vnd.ms-powerpoint.slide.macroenabled.12'], + 'sldx' => ['application/vnd.openxmlformats-officedocument.presentationml.slide'], + 'slice' => ['text/x-systemd-unit'], + 'slk' => ['text/spreadsheet'], + 'slt' => ['application/vnd.epson.salt'], + 'sm' => ['application/vnd.stepmania.stepchart'], + 'smaf' => ['application/vnd.smaf', 'application/x-smaf'], + 'smc' => ['application/vnd.nintendo.snes.rom', 'application/x-snes-rom'], + 'smd' => ['application/vnd.stardivision.mail', 'application/x-genesis-rom'], + 'smf' => ['application/vnd.stardivision.math'], + 'smi' => ['application/smil', 'application/smil+xml', 'application/x-sami'], + 'smil' => ['application/smil', 'application/smil+xml'], + 'sml' => ['application/smil', 'application/smil+xml'], + 'sms' => ['application/x-sms-rom'], + 'smv' => ['video/x-smv'], + 'smzip' => ['application/vnd.stepmania.package'], + 'snap' => ['application/vnd.snap'], + 'snd' => ['audio/basic'], + 'snf' => ['application/x-font-snf'], + 'so' => ['application/octet-stream', 'application/x-sharedlib'], + 'socket' => ['text/x-systemd-unit'], + 'spc' => ['application/x-pkcs7-certificates'], + 'spd' => ['application/x-font-speedo'], + 'spec' => ['text/x-rpm-spec'], + 'spf' => ['application/vnd.yamaha.smaf-phrase'], + 'spl' => ['application/futuresplash', 'application/vnd.adobe.flash.movie', 'application/x-futuresplash', 'application/x-shockwave-flash'], + 'spm' => ['application/x-source-rpm'], + 'spot' => ['text/vnd.in3d.spot'], + 'spp' => ['application/scvp-vp-response'], + 'spq' => ['application/scvp-vp-request'], + 'spx' => ['audio/ogg', 'audio/x-speex'], + 'sql' => ['application/sql', 'application/x-sql', 'text/x-sql'], + 'sqlite2' => ['application/x-sqlite2'], + 'sqlite3' => ['application/vnd.sqlite3', 'application/x-sqlite3'], + 'sqsh' => ['application/vnd.squashfs'], + 'sr2' => ['image/x-sony-sr2'], + 'src' => ['application/x-wais-source'], + 'src.rpm' => ['application/x-source-rpm'], + 'srf' => ['image/x-sony-srf'], + 'srt' => ['application/x-srt', 'application/x-subrip'], + 'sru' => ['application/sru+xml'], + 'srx' => ['application/sparql-results+xml'], + 'ss' => ['text/x-scheme'], + 'ssa' => ['text/x-ssa'], + 'ssdl' => ['application/ssdl+xml'], + 'sse' => ['application/vnd.kodak-descriptor'], + 'ssf' => ['application/vnd.epson.ssf'], + 'ssml' => ['application/ssml+xml'], + 'st' => ['application/vnd.sailingtracker.track'], + 'stc' => ['application/vnd.sun.xml.calc.template'], + 'std' => ['application/vnd.sun.xml.draw.template'], + 'stf' => ['application/vnd.wt.stf'], + 'sti' => ['application/vnd.sun.xml.impress.template'], + 'stk' => ['application/hyperstudio'], + 'stl' => ['application/vnd.ms-pki.stl', 'model/stl', 'model/x.stl-ascii', 'model/x.stl-binary'], + 'stm' => ['audio/x-stm'], + 'str' => ['application/vnd.pg.format'], + 'stw' => ['application/vnd.sun.xml.writer.template'], + 'sty' => ['application/x-tex', 'text/x-tex'], + 'sub' => ['image/vnd.dvb.subtitle', 'text/vnd.dvb.subtitle', 'text/x-microdvd', 'text/x-mpsub', 'text/x-subviewer'], + 'sun' => ['image/x-sun-raster'], + 'sus' => ['application/vnd.sus-calendar'], + 'susp' => ['application/vnd.sus-calendar'], + 'sv' => ['text/x-svsrc'], + 'sv4cpio' => ['application/x-sv4cpio'], + 'sv4crc' => ['application/x-sv4crc'], + 'svc' => ['application/vnd.dvb.service'], + 'svd' => ['application/vnd.svd'], + 'svg' => ['image/svg+xml', 'image/svg'], + 'svgz' => ['image/svg+xml', 'image/svg+xml-compressed'], + 'svh' => ['text/x-svhdr'], + 'swa' => ['application/x-director'], + 'swap' => ['text/x-systemd-unit'], + 'swf' => ['application/futuresplash', 'application/vnd.adobe.flash.movie', 'application/x-shockwave-flash'], + 'swi' => ['application/vnd.aristanetworks.swi'], + 'swm' => ['application/x-ms-wim'], + 'sxc' => ['application/vnd.sun.xml.calc'], + 'sxd' => ['application/vnd.sun.xml.draw'], + 'sxg' => ['application/vnd.sun.xml.writer.global'], + 'sxi' => ['application/vnd.sun.xml.impress'], + 'sxm' => ['application/vnd.sun.xml.math'], + 'sxw' => ['application/vnd.sun.xml.writer'], + 'sylk' => ['text/spreadsheet'], + 't' => ['application/x-perl', 'application/x-troff', 'text/troff', 'text/x-perl', 'text/x-troff'], + 't2t' => ['text/x-txt2tags'], + 't3' => ['application/x-t3vm-image'], + 'taglet' => ['application/vnd.mynfc'], + 'tao' => ['application/vnd.tao.intent-module-archive'], + 'tar' => ['application/x-tar', 'application/x-gtar'], + 'tar.Z' => ['application/x-tarz'], + 'tar.bz' => ['application/x-bzip-compressed-tar'], + 'tar.bz2' => ['application/x-bzip-compressed-tar'], + 'tar.gz' => ['application/x-compressed-tar'], + 'tar.lrz' => ['application/x-lrzip-compressed-tar'], + 'tar.lz' => ['application/x-lzip-compressed-tar'], + 'tar.lz4' => ['application/x-lz4-compressed-tar'], + 'tar.lzma' => ['application/x-lzma-compressed-tar'], + 'tar.lzo' => ['application/x-tzo'], + 'tar.xz' => ['application/x-xz-compressed-tar'], + 'target' => ['text/x-systemd-unit'], + 'taz' => ['application/x-tarz'], + 'tb2' => ['application/x-bzip-compressed-tar'], + 'tbz' => ['application/x-bzip-compressed-tar'], + 'tbz2' => ['application/x-bzip-compressed-tar'], + 'tcap' => ['application/vnd.3gpp2.tcap'], + 'tcl' => ['application/x-tcl', 'text/x-tcl'], + 'teacher' => ['application/vnd.smart.teacher'], + 'tei' => ['application/tei+xml'], + 'teicorpus' => ['application/tei+xml'], + 'tex' => ['application/x-tex', 'text/x-tex'], + 'texi' => ['application/x-texinfo', 'text/x-texinfo'], + 'texinfo' => ['application/x-texinfo', 'text/x-texinfo'], + 'text' => ['text/plain'], + 'tfi' => ['application/thraud+xml'], + 'tfm' => ['application/x-tex-tfm'], + 'tga' => ['image/x-icb', 'image/x-tga'], + 'tgz' => ['application/x-compressed-tar'], + 'theme' => ['application/x-theme'], + 'themepack' => ['application/x-windows-themepack'], + 'thmx' => ['application/vnd.ms-officetheme'], + 'tif' => ['image/tiff'], + 'tiff' => ['image/tiff'], + 'timer' => ['text/x-systemd-unit'], + 'tk' => ['text/x-tcl'], + 'tlrz' => ['application/x-lrzip-compressed-tar'], + 'tlz' => ['application/x-lzma-compressed-tar'], + 'tmo' => ['application/vnd.tmobile-livetv'], + 'tnef' => ['application/ms-tnef', 'application/vnd.ms-tnef'], + 'tnf' => ['application/ms-tnef', 'application/vnd.ms-tnef'], + 'toc' => ['application/x-cdrdao-toc'], + 'torrent' => ['application/x-bittorrent'], + 'tpic' => ['image/x-icb', 'image/x-tga'], + 'tpl' => ['application/vnd.groove-tool-template'], + 'tpt' => ['application/vnd.trid.tpt'], + 'tr' => ['application/x-troff', 'text/troff', 'text/x-troff'], + 'tra' => ['application/vnd.trueapp'], + 'trig' => ['application/trig', 'application/x-trig'], + 'trm' => ['application/x-msterminal'], + 'ts' => ['application/x-linguist', 'text/vnd.qt.linguist', 'text/vnd.trolltech.linguist', 'video/mp2t'], + 'tsd' => ['application/timestamped-data'], + 'tsv' => ['text/tab-separated-values'], + 'tta' => ['audio/tta', 'audio/x-tta'], + 'ttc' => ['font/collection'], + 'ttf' => ['application/x-font-truetype', 'application/x-font-ttf', 'font/ttf'], + 'ttl' => ['text/turtle'], + 'ttx' => ['application/x-font-ttx'], + 'twd' => ['application/vnd.simtech-mindmapper'], + 'twds' => ['application/vnd.simtech-mindmapper'], + 'twig' => ['text/x-twig'], + 'txd' => ['application/vnd.genomatix.tuxedo'], + 'txf' => ['application/vnd.mobius.txf'], + 'txt' => ['text/plain'], + 'txz' => ['application/x-xz-compressed-tar'], + 'tzo' => ['application/x-tzo'], + 'u32' => ['application/x-authorware-bin'], + 'udeb' => ['application/vnd.debian.binary-package', 'application/x-deb', 'application/x-debian-package'], + 'ufd' => ['application/vnd.ufdl'], + 'ufdl' => ['application/vnd.ufdl'], + 'ufraw' => ['application/x-ufraw'], + 'ui' => ['application/x-designer', 'application/x-gtk-builder'], + 'uil' => ['text/x-uil'], + 'ult' => ['audio/x-mod'], + 'ulx' => ['application/x-glulx'], + 'umj' => ['application/vnd.umajin'], + 'unf' => ['application/x-nes-rom'], + 'uni' => ['audio/x-mod'], + 'unif' => ['application/x-nes-rom'], + 'unityweb' => ['application/vnd.unity'], + 'uoml' => ['application/vnd.uoml+xml'], + 'uri' => ['text/uri-list'], + 'uris' => ['text/uri-list'], + 'url' => ['application/x-mswinurl'], + 'urls' => ['text/uri-list'], + 'ustar' => ['application/x-ustar'], + 'utz' => ['application/vnd.uiq.theme'], + 'uu' => ['text/x-uuencode'], + 'uue' => ['text/x-uuencode', 'zz-application/zz-winassoc-uu'], + 'uva' => ['audio/vnd.dece.audio'], + 'uvd' => ['application/vnd.dece.data'], + 'uvf' => ['application/vnd.dece.data'], + 'uvg' => ['image/vnd.dece.graphic'], + 'uvh' => ['video/vnd.dece.hd'], + 'uvi' => ['image/vnd.dece.graphic'], + 'uvm' => ['video/vnd.dece.mobile'], + 'uvp' => ['video/vnd.dece.pd'], + 'uvs' => ['video/vnd.dece.sd'], + 'uvt' => ['application/vnd.dece.ttml+xml'], + 'uvu' => ['video/vnd.uvvu.mp4'], + 'uvv' => ['video/vnd.dece.video'], + 'uvva' => ['audio/vnd.dece.audio'], + 'uvvd' => ['application/vnd.dece.data'], + 'uvvf' => ['application/vnd.dece.data'], + 'uvvg' => ['image/vnd.dece.graphic'], + 'uvvh' => ['video/vnd.dece.hd'], + 'uvvi' => ['image/vnd.dece.graphic'], + 'uvvm' => ['video/vnd.dece.mobile'], + 'uvvp' => ['video/vnd.dece.pd'], + 'uvvs' => ['video/vnd.dece.sd'], + 'uvvt' => ['application/vnd.dece.ttml+xml'], + 'uvvu' => ['video/vnd.uvvu.mp4'], + 'uvvv' => ['video/vnd.dece.video'], + 'uvvx' => ['application/vnd.dece.unspecified'], + 'uvvz' => ['application/vnd.dece.zip'], + 'uvx' => ['application/vnd.dece.unspecified'], + 'uvz' => ['application/vnd.dece.zip'], + 'v' => ['text/x-verilog'], + 'v64' => ['application/x-n64-rom'], + 'vala' => ['text/x-vala'], + 'vapi' => ['text/x-vala'], + 'vb' => ['application/x-virtual-boy-rom'], + 'vcard' => ['text/directory', 'text/vcard', 'text/x-vcard'], + 'vcd' => ['application/x-cdlink'], + 'vcf' => ['text/x-vcard', 'text/directory', 'text/vcard'], + 'vcg' => ['application/vnd.groove-vcard'], + 'vcs' => ['application/ics', 'text/calendar', 'text/x-vcalendar'], + 'vct' => ['text/directory', 'text/vcard', 'text/x-vcard'], + 'vcx' => ['application/vnd.vcx'], + 'vda' => ['image/x-icb', 'image/x-tga'], + 'vhd' => ['text/x-vhdl'], + 'vhdl' => ['text/x-vhdl'], + 'vis' => ['application/vnd.visionary'], + 'viv' => ['video/vivo', 'video/vnd.vivo'], + 'vivo' => ['video/vivo', 'video/vnd.vivo'], + 'vlc' => ['application/m3u', 'audio/m3u', 'audio/mpegurl', 'audio/x-m3u', 'audio/x-mp3-playlist', 'audio/x-mpegurl'], + 'vob' => ['video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2', 'video/x-ms-vob'], + 'voc' => ['audio/x-voc'], + 'vor' => ['application/vnd.stardivision.writer', 'application/vnd.stardivision.writer-global'], + 'vox' => ['application/x-authorware-bin'], + 'vrm' => ['model/vrml'], + 'vrml' => ['model/vrml'], + 'vsd' => ['application/vnd.visio'], + 'vsdm' => ['application/vnd.ms-visio.drawing.macroenabled.main+xml'], + 'vsdx' => ['application/vnd.ms-visio.drawing.main+xml'], + 'vsf' => ['application/vnd.vsf'], + 'vss' => ['application/vnd.visio'], + 'vssm' => ['application/vnd.ms-visio.stencil.macroenabled.main+xml'], + 'vssx' => ['application/vnd.ms-visio.stencil.main+xml'], + 'vst' => ['application/vnd.visio', 'image/x-icb', 'image/x-tga'], + 'vstm' => ['application/vnd.ms-visio.template.macroenabled.main+xml'], + 'vstx' => ['application/vnd.ms-visio.template.main+xml'], + 'vsw' => ['application/vnd.visio'], + 'vtt' => ['text/vtt'], + 'vtu' => ['model/vnd.vtu'], + 'vxml' => ['application/voicexml+xml'], + 'w3d' => ['application/x-director'], + 'wad' => ['application/x-doom', 'application/x-doom-wad', 'application/x-wii-wad'], + 'wav' => ['audio/wav', 'audio/vnd.wave', 'audio/x-wav'], + 'wax' => ['application/x-ms-asx', 'audio/x-ms-asx', 'audio/x-ms-wax', 'video/x-ms-wax', 'video/x-ms-wmx', 'video/x-ms-wvx'], + 'wb1' => ['application/x-quattropro'], + 'wb2' => ['application/x-quattropro'], + 'wb3' => ['application/x-quattropro'], + 'wbmp' => ['image/vnd.wap.wbmp'], + 'wbs' => ['application/vnd.criticaltools.wbs+xml'], + 'wbxml' => ['application/vnd.wap.wbxml'], + 'wcm' => ['application/vnd.ms-works'], + 'wdb' => ['application/vnd.ms-works'], + 'wdp' => ['image/vnd.ms-photo'], + 'weba' => ['audio/webm'], + 'webm' => ['video/webm'], + 'webp' => ['image/webp'], + 'wg' => ['application/vnd.pmi.widget'], + 'wgt' => ['application/widget'], + 'wim' => ['application/x-ms-wim'], + 'wk1' => ['application/lotus123', 'application/vnd.lotus-1-2-3', 'application/wk1', 'application/x-123', 'application/x-lotus123', 'zz-application/zz-winassoc-123'], + 'wk3' => ['application/lotus123', 'application/vnd.lotus-1-2-3', 'application/wk1', 'application/x-123', 'application/x-lotus123', 'zz-application/zz-winassoc-123'], + 'wk4' => ['application/lotus123', 'application/vnd.lotus-1-2-3', 'application/wk1', 'application/x-123', 'application/x-lotus123', 'zz-application/zz-winassoc-123'], + 'wkdownload' => ['application/x-partial-download'], + 'wks' => ['application/lotus123', 'application/vnd.lotus-1-2-3', 'application/vnd.ms-works', 'application/wk1', 'application/x-123', 'application/x-lotus123', 'zz-application/zz-winassoc-123'], + 'wm' => ['video/x-ms-wm'], + 'wma' => ['audio/x-ms-wma', 'audio/wma'], + 'wmd' => ['application/x-ms-wmd'], + 'wmf' => ['application/wmf', 'application/x-msmetafile', 'application/x-wmf', 'image/wmf', 'image/x-win-metafile', 'image/x-wmf'], + 'wml' => ['text/vnd.wap.wml'], + 'wmlc' => ['application/vnd.wap.wmlc'], + 'wmls' => ['text/vnd.wap.wmlscript'], + 'wmlsc' => ['application/vnd.wap.wmlscriptc'], + 'wmv' => ['audio/x-ms-wmv', 'video/x-ms-wmv'], + 'wmx' => ['application/x-ms-asx', 'audio/x-ms-asx', 'video/x-ms-wax', 'video/x-ms-wmx', 'video/x-ms-wvx'], + 'wmz' => ['application/x-ms-wmz', 'application/x-msmetafile'], + 'woff' => ['application/font-woff', 'application/x-font-woff', 'font/woff'], + 'woff2' => ['font/woff', 'font/woff2'], + 'wp' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'], + 'wp4' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'], + 'wp5' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'], + 'wp6' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'], + 'wpd' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'], + 'wpg' => ['application/x-wpg'], + 'wpl' => ['application/vnd.ms-wpl'], + 'wpp' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'], + 'wps' => ['application/vnd.ms-works'], + 'wqd' => ['application/vnd.wqd'], + 'wri' => ['application/x-mswrite'], + 'wrl' => ['model/vrml'], + 'ws' => ['application/x-wonderswan-rom'], + 'wsc' => ['application/x-wonderswan-color-rom'], + 'wsdl' => ['application/wsdl+xml'], + 'wsgi' => ['text/x-python'], + 'wspolicy' => ['application/wspolicy+xml'], + 'wtb' => ['application/vnd.webturbo'], + 'wv' => ['audio/x-wavpack'], + 'wvc' => ['audio/x-wavpack-correction'], + 'wvp' => ['audio/x-wavpack'], + 'wvx' => ['application/x-ms-asx', 'audio/x-ms-asx', 'video/x-ms-wax', 'video/x-ms-wmx', 'video/x-ms-wvx'], + 'wwf' => ['application/wwf', 'application/x-wwf'], + 'x32' => ['application/x-authorware-bin'], + 'x3d' => ['model/x3d+xml'], + 'x3db' => ['model/x3d+binary'], + 'x3dbz' => ['model/x3d+binary'], + 'x3dv' => ['model/x3d+vrml'], + 'x3dvz' => ['model/x3d+vrml'], + 'x3dz' => ['model/x3d+xml'], + 'x3f' => ['image/x-sigma-x3f'], + 'xac' => ['application/x-gnucash'], + 'xaml' => ['application/xaml+xml'], + 'xap' => ['application/x-silverlight-app'], + 'xar' => ['application/vnd.xara', 'application/x-xar'], + 'xbap' => ['application/x-ms-xbap'], + 'xbd' => ['application/vnd.fujixerox.docuworks.binder'], + 'xbel' => ['application/x-xbel'], + 'xbl' => ['application/xml', 'text/xml'], + 'xbm' => ['image/x-xbitmap'], + 'xcf' => ['image/x-xcf'], + 'xcf.bz2' => ['image/x-compressed-xcf'], + 'xcf.gz' => ['image/x-compressed-xcf'], + 'xdf' => ['application/xcap-diff+xml'], + 'xdgapp' => ['application/vnd.flatpak', 'application/vnd.xdgapp'], + 'xdm' => ['application/vnd.syncml.dm+xml'], + 'xdp' => ['application/vnd.adobe.xdp+xml'], + 'xdssc' => ['application/dssc+xml'], + 'xdw' => ['application/vnd.fujixerox.docuworks'], + 'xenc' => ['application/xenc+xml'], + 'xer' => ['application/patch-ops-error+xml'], + 'xfdf' => ['application/vnd.adobe.xfdf'], + 'xfdl' => ['application/vnd.xfdl'], + 'xhe' => ['audio/usac'], + 'xht' => ['application/xhtml+xml'], + 'xhtml' => ['application/xhtml+xml'], + 'xhvml' => ['application/xv+xml'], + 'xi' => ['audio/x-xi'], + 'xif' => ['image/vnd.xiff'], + 'xla' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xlam' => ['application/vnd.ms-excel.addin.macroenabled.12'], + 'xlc' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xld' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xlf' => ['application/x-xliff', 'application/x-xliff+xml', 'application/xliff+xml'], + 'xliff' => ['application/x-xliff', 'application/xliff+xml'], + 'xll' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xlm' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xlr' => ['application/vnd.ms-works'], + 'xls' => ['application/vnd.ms-excel', 'application/msexcel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xlsb' => ['application/vnd.ms-excel.sheet.binary.macroenabled.12'], + 'xlsm' => ['application/vnd.ms-excel.sheet.macroenabled.12'], + 'xlsx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'], + 'xlt' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xltm' => ['application/vnd.ms-excel.template.macroenabled.12'], + 'xltx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.template'], + 'xlw' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'], + 'xm' => ['audio/x-xm', 'audio/xm'], + 'xmf' => ['audio/mobile-xmf', 'audio/x-xmf', 'audio/xmf'], + 'xmi' => ['text/x-xmi'], + 'xml' => ['application/xml', 'text/xml'], + 'xo' => ['application/vnd.olpc-sugar'], + 'xop' => ['application/xop+xml'], + 'xpi' => ['application/x-xpinstall'], + 'xpl' => ['application/xproc+xml'], + 'xpm' => ['image/x-xpixmap', 'image/x-xpm'], + 'xpr' => ['application/vnd.is-xpr'], + 'xps' => ['application/oxps', 'application/vnd.ms-xpsdocument', 'application/xps'], + 'xpw' => ['application/vnd.intercon.formnet'], + 'xpx' => ['application/vnd.intercon.formnet'], + 'xsd' => ['application/xml', 'text/xml'], + 'xsl' => ['application/xml', 'application/xslt+xml'], + 'xslfo' => ['text/x-xslfo'], + 'xslt' => ['application/xslt+xml'], + 'xsm' => ['application/vnd.syncml+xml'], + 'xspf' => ['application/x-xspf+xml', 'application/xspf+xml'], + 'xul' => ['application/vnd.mozilla.xul+xml'], + 'xvm' => ['application/xv+xml'], + 'xvml' => ['application/xv+xml'], + 'xwd' => ['image/x-xwindowdump'], + 'xyz' => ['chemical/x-xyz'], + 'xz' => ['application/x-xz'], + 'yaml' => ['application/x-yaml', 'text/x-yaml', 'text/yaml'], + 'yang' => ['application/yang'], + 'yin' => ['application/yin+xml'], + 'yml' => ['application/x-yaml', 'text/x-yaml', 'text/yaml'], + 'yt' => ['application/vnd.youtube.yt'], + 'z1' => ['application/x-zmachine'], + 'z2' => ['application/x-zmachine'], + 'z3' => ['application/x-zmachine'], + 'z4' => ['application/x-zmachine'], + 'z5' => ['application/x-zmachine'], + 'z6' => ['application/x-zmachine'], + 'z64' => ['application/x-n64-rom'], + 'z7' => ['application/x-zmachine'], + 'z8' => ['application/x-zmachine'], + 'zabw' => ['application/x-abiword'], + 'zaz' => ['application/vnd.zzazz.deck+xml'], + 'zip' => ['application/zip', 'application/x-zip', 'application/x-zip-compressed'], + 'zir' => ['application/vnd.zul'], + 'zirz' => ['application/vnd.zul'], + 'zmm' => ['application/vnd.handheld-entertainment+xml'], + 'zoo' => ['application/x-zoo'], + 'zsav' => ['application/x-spss-sav', 'application/x-spss-savefile'], + 'zz' => ['application/zlib'], + '123' => ['application/lotus123', 'application/vnd.lotus-1-2-3', 'application/wk1', 'application/x-123', 'application/x-lotus123', 'zz-application/zz-winassoc-123'], + '602' => ['application/x-t602'], + '669' => ['audio/x-mod'], + ]; +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/MimeTypesInterface.php b/lam/lib/3rdParty/composer/symfony/mime/MimeTypesInterface.php new file mode 100644 index 00000000..9fbd2cc2 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/MimeTypesInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +/** + * @author Fabien Potencier + */ +interface MimeTypesInterface extends MimeTypeGuesserInterface +{ + /** + * Gets the extensions for the given MIME type. + * + * @return string[] an array of extensions (first one is the preferred one) + */ + public function getExtensions(string $mimeType): array; + + /** + * Gets the MIME types for the given extension. + * + * @return string[] an array of MIME types (first one is the preferred one) + */ + public function getMimeTypes(string $ext): array; +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Part/AbstractMultipartPart.php b/lam/lib/3rdParty/composer/symfony/mime/Part/AbstractMultipartPart.php new file mode 100644 index 00000000..48b86202 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Part/AbstractMultipartPart.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part; + +use Symfony\Component\Mime\Header\Headers; + +/** + * @author Fabien Potencier + */ +abstract class AbstractMultipartPart extends AbstractPart +{ + private $boundary; + private $parts = []; + + public function __construct(AbstractPart ...$parts) + { + parent::__construct(); + + foreach ($parts as $part) { + $this->parts[] = $part; + } + } + + /** + * @return AbstractPart[] + */ + public function getParts(): array + { + return $this->parts; + } + + public function getMediaType(): string + { + return 'multipart'; + } + + public function getPreparedHeaders(): Headers + { + $headers = parent::getPreparedHeaders(); + $headers->setHeaderParameter('Content-Type', 'boundary', $this->getBoundary()); + + return $headers; + } + + public function bodyToString(): string + { + $parts = $this->getParts(); + $string = ''; + foreach ($parts as $part) { + $string .= '--'.$this->getBoundary()."\r\n".$part->toString()."\r\n"; + } + $string .= '--'.$this->getBoundary()."--\r\n"; + + return $string; + } + + public function bodyToIterable(): iterable + { + $parts = $this->getParts(); + foreach ($parts as $part) { + yield '--'.$this->getBoundary()."\r\n"; + yield from $part->toIterable(); + yield "\r\n"; + } + yield '--'.$this->getBoundary()."--\r\n"; + } + + public function asDebugString(): string + { + $str = parent::asDebugString(); + foreach ($this->getParts() as $part) { + $lines = explode("\n", $part->asDebugString()); + $str .= "\n └ ".array_shift($lines); + foreach ($lines as $line) { + $str .= "\n |".$line; + } + } + + return $str; + } + + private function getBoundary(): string + { + if (null === $this->boundary) { + $this->boundary = '_=_symfony_'.time().'_'.bin2hex(random_bytes(16)).'_=_'; + } + + return $this->boundary; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Part/AbstractPart.php b/lam/lib/3rdParty/composer/symfony/mime/Part/AbstractPart.php new file mode 100644 index 00000000..93892d9d --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Part/AbstractPart.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part; + +use Symfony\Component\Mime\Header\Headers; + +/** + * @author Fabien Potencier + */ +abstract class AbstractPart +{ + private $headers; + + public function __construct() + { + $this->headers = new Headers(); + } + + public function getHeaders(): Headers + { + return $this->headers; + } + + public function getPreparedHeaders(): Headers + { + $headers = clone $this->headers; + $headers->setHeaderBody('Parameterized', 'Content-Type', $this->getMediaType().'/'.$this->getMediaSubtype()); + + return $headers; + } + + public function toString(): string + { + return $this->getPreparedHeaders()->toString()."\r\n".$this->bodyToString(); + } + + public function toIterable(): iterable + { + yield $this->getPreparedHeaders()->toString(); + yield "\r\n"; + yield from $this->bodyToIterable(); + } + + public function asDebugString(): string + { + return $this->getMediaType().'/'.$this->getMediaSubtype(); + } + + abstract public function bodyToString(): string; + + abstract public function bodyToIterable(): iterable; + + abstract public function getMediaType(): string; + + abstract public function getMediaSubtype(): string; +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Part/DataPart.php b/lam/lib/3rdParty/composer/symfony/mime/Part/DataPart.php new file mode 100644 index 00000000..423185fe --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Part/DataPart.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part; + +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Header\Headers; +use Symfony\Component\Mime\MimeTypes; + +/** + * @author Fabien Potencier + */ +class DataPart extends TextPart +{ + private static $mimeTypes; + + private $filename; + private $mediaType; + private $cid; + private $handle; + + /** + * @param resource|string $body + */ + public function __construct($body, string $filename = null, string $contentType = null, string $encoding = null) + { + if (null === $contentType) { + $contentType = 'application/octet-stream'; + } + list($this->mediaType, $subtype) = explode('/', $contentType); + + parent::__construct($body, null, $subtype, $encoding); + + $this->filename = $filename; + $this->setName($filename); + $this->setDisposition('attachment'); + } + + public static function fromPath(string $path, string $name = null, string $contentType = null): self + { + // FIXME: if file is not readable, exception? + + if (null === $contentType) { + $ext = strtolower(substr($path, strrpos($path, '.') + 1)); + if (null === self::$mimeTypes) { + self::$mimeTypes = new MimeTypes(); + } + $contentType = self::$mimeTypes->getMimeTypes($ext)[0] ?? 'application/octet-stream'; + } + + if (false === $handle = @fopen($path, 'r', false)) { + throw new InvalidArgumentException(sprintf('Unable to open path "%s"', $path)); + } + $p = new self($handle, $name ?: basename($path), $contentType); + $p->handle = $handle; + + return $p; + } + + /** + * @return $this + */ + public function asInline() + { + return $this->setDisposition('inline'); + } + + public function getContentId(): string + { + return $this->cid ?: $this->cid = $this->generateContentId(); + } + + public function hasContentId(): bool + { + return null !== $this->cid; + } + + public function getMediaType(): string + { + return $this->mediaType; + } + + public function getPreparedHeaders(): Headers + { + $headers = parent::getPreparedHeaders(); + + if (null !== $this->cid) { + $headers->setHeaderBody('Id', 'Content-ID', $this->cid); + } + + if (null !== $this->filename) { + $headers->setHeaderParameter('Content-Disposition', 'filename', $this->filename); + } + + return $headers; + } + + public function asDebugString(): string + { + $str = parent::asDebugString(); + if (null !== $this->filename) { + $str .= ' filename: '.$this->filename; + } + + return $str; + } + + private function generateContentId(): string + { + return bin2hex(random_bytes(16)).'@symfony'; + } + + public function __destruct() + { + if (null !== $this->handle && \is_resource($this->handle)) { + fclose($this->handle); + } + } + + /** + * @return array + */ + public function __sleep() + { + // converts the body to a string + parent::__sleep(); + + $this->_parent = []; + foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) { + $r = new \ReflectionProperty(TextPart::class, $name); + $r->setAccessible(true); + $this->_parent[$name] = $r->getValue($this); + } + $this->_headers = $this->getHeaders(); + + return ['_headers', '_parent', 'filename', 'mediaType']; + } + + public function __wakeup() + { + $r = new \ReflectionProperty(AbstractPart::class, 'headers'); + $r->setAccessible(true); + $r->setValue($this, $this->_headers); + unset($this->_headers); + + foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) { + $r = new \ReflectionProperty(TextPart::class, $name); + $r->setAccessible(true); + $r->setValue($this, $this->_parent[$name]); + } + unset($this->_parent); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Part/MessagePart.php b/lam/lib/3rdParty/composer/symfony/mime/Part/MessagePart.php new file mode 100644 index 00000000..1b5c23e2 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Part/MessagePart.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part; + +use Symfony\Component\Mime\Message; +use Symfony\Component\Mime\RawMessage; + +/** + * @final + * + * @author Fabien Potencier + */ +class MessagePart extends DataPart +{ + private $message; + + public function __construct(RawMessage $message) + { + if ($message instanceof Message) { + $name = $message->getHeaders()->getHeaderBody('Subject').'.eml'; + } else { + $name = 'email.eml'; + } + parent::__construct('', $name); + + $this->message = $message; + } + + public function getMediaType(): string + { + return 'message'; + } + + public function getMediaSubtype(): string + { + return 'rfc822'; + } + + public function getBody(): string + { + return $this->message->toString(); + } + + public function bodyToString(): string + { + return $this->getBody(); + } + + public function bodyToIterable(): iterable + { + return $this->message->toIterable(); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/AlternativePart.php b/lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/AlternativePart.php new file mode 100644 index 00000000..fd754234 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/AlternativePart.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part\Multipart; + +use Symfony\Component\Mime\Part\AbstractMultipartPart; + +/** + * @author Fabien Potencier + */ +final class AlternativePart extends AbstractMultipartPart +{ + public function getMediaSubtype(): string + { + return 'alternative'; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/DigestPart.php b/lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/DigestPart.php new file mode 100644 index 00000000..27537f15 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/DigestPart.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part\Multipart; + +use Symfony\Component\Mime\Part\AbstractMultipartPart; +use Symfony\Component\Mime\Part\MessagePart; + +/** + * @author Fabien Potencier + */ +final class DigestPart extends AbstractMultipartPart +{ + public function __construct(MessagePart ...$parts) + { + parent::__construct(...$parts); + } + + public function getMediaSubtype(): string + { + return 'digest'; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/FormDataPart.php b/lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/FormDataPart.php new file mode 100644 index 00000000..88aa1a31 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/FormDataPart.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part\Multipart; + +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Part\AbstractMultipartPart; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\TextPart; + +/** + * Implements RFC 7578. + * + * @author Fabien Potencier + */ +final class FormDataPart extends AbstractMultipartPart +{ + private $fields = []; + + /** + * @param (string|array|DataPart)[] $fields + */ + public function __construct(array $fields = []) + { + parent::__construct(); + + foreach ($fields as $name => $value) { + if (!\is_string($value) && !\is_array($value) && !$value instanceof TextPart) { + throw new InvalidArgumentException(sprintf('A form field value can only be a string, an array, or an instance of TextPart ("%s" given).', \is_object($value) ? \get_class($value) : \gettype($value))); + } + + $this->fields[$name] = $value; + } + // HTTP does not support \r\n in header values + $this->getHeaders()->setMaxLineLength(PHP_INT_MAX); + } + + public function getMediaSubtype(): string + { + return 'form-data'; + } + + public function getParts(): array + { + return $this->prepareFields($this->fields); + } + + private function prepareFields(array $fields): array + { + $values = []; + array_walk_recursive($fields, function ($item, $key) use (&$values) { + if (!\is_array($item)) { + $values[] = $this->preparePart($key, $item); + } + }); + + return $values; + } + + private function preparePart(string $name, $value): TextPart + { + if (\is_string($value)) { + return $this->configurePart($name, new TextPart($value, 'utf-8', 'plain', '8bit')); + } + + return $this->configurePart($name, $value); + } + + private function configurePart(string $name, TextPart $part): TextPart + { + static $r; + + if (null === $r) { + $r = new \ReflectionProperty(TextPart::class, 'encoding'); + $r->setAccessible(true); + } + + $part->setDisposition('form-data'); + $part->setName($name); + // HTTP does not support \r\n in header values + $part->getHeaders()->setMaxLineLength(PHP_INT_MAX); + $r->setValue($part, '8bit'); + + return $part; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/MixedPart.php b/lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/MixedPart.php new file mode 100644 index 00000000..c8d7028c --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/MixedPart.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part\Multipart; + +use Symfony\Component\Mime\Part\AbstractMultipartPart; + +/** + * @author Fabien Potencier + */ +final class MixedPart extends AbstractMultipartPart +{ + public function getMediaSubtype(): string + { + return 'mixed'; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/RelatedPart.php b/lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/RelatedPart.php new file mode 100644 index 00000000..08fdd5fa --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Part/Multipart/RelatedPart.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part\Multipart; + +use Symfony\Component\Mime\Part\AbstractMultipartPart; +use Symfony\Component\Mime\Part\AbstractPart; + +/** + * @author Fabien Potencier + */ +final class RelatedPart extends AbstractMultipartPart +{ + private $mainPart; + + public function __construct(AbstractPart $mainPart, AbstractPart $part, AbstractPart ...$parts) + { + $this->mainPart = $mainPart; + $this->prepareParts($part, ...$parts); + + parent::__construct($part, ...$parts); + } + + public function getParts(): array + { + return array_merge([$this->mainPart], parent::getParts()); + } + + public function getMediaSubtype(): string + { + return 'related'; + } + + private function generateContentId(): string + { + return bin2hex(random_bytes(16)).'@symfony'; + } + + private function prepareParts(AbstractPart ...$parts): void + { + foreach ($parts as $part) { + if (!$part->getHeaders()->has('Content-ID')) { + $part->getHeaders()->setHeaderBody('Id', 'Content-ID', $this->generateContentId()); + } + } + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Part/SMimePart.php b/lam/lib/3rdParty/composer/symfony/mime/Part/SMimePart.php new file mode 100644 index 00000000..1dfc1aef --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Part/SMimePart.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part; + +use Symfony\Component\Mime\Header\Headers; + +/** + * @author Sebastiaan Stok + */ +class SMimePart extends AbstractPart +{ + private $body; + private $type; + private $subtype; + private $parameters; + + /** + * @param iterable|string $body + */ + public function __construct($body, string $type, string $subtype, array $parameters) + { + parent::__construct(); + + if (!\is_string($body) && !is_iterable($body)) { + throw new \TypeError(sprintf('The body of "%s" must be a string or a iterable (got "%s").', self::class, \is_object($body) ? \get_class($body) : \gettype($body))); + } + + $this->body = $body; + $this->type = $type; + $this->subtype = $subtype; + $this->parameters = $parameters; + } + + public function getMediaType(): string + { + return $this->type; + } + + public function getMediaSubtype(): string + { + return $this->subtype; + } + + public function bodyToString(): string + { + if (\is_string($this->body)) { + return $this->body; + } + + $body = ''; + foreach ($this->body as $chunk) { + $body .= $chunk; + } + $this->body = $body; + + return $body; + } + + public function bodyToIterable(): iterable + { + if (\is_string($this->body)) { + yield $this->body; + + return; + } + + $body = ''; + foreach ($this->body as $chunk) { + $body .= $chunk; + yield $chunk; + } + $this->body = $body; + } + + public function getPreparedHeaders(): Headers + { + $headers = clone parent::getHeaders(); + + $headers->setHeaderBody('Parameterized', 'Content-Type', $this->getMediaType().'/'.$this->getMediaSubtype()); + + foreach ($this->parameters as $name => $value) { + $headers->setHeaderParameter('Content-Type', $name, $value); + } + + return $headers; + } + + public function __sleep(): array + { + // convert iterables to strings for serialization + if (is_iterable($this->body)) { + $this->body = $this->bodyToString(); + } + + $this->_headers = $this->getHeaders(); + + return ['_headers', 'body', 'type', 'subtype', 'parameters']; + } + + public function __wakeup(): void + { + $r = new \ReflectionProperty(AbstractPart::class, 'headers'); + $r->setAccessible(true); + $r->setValue($this, $this->_headers); + unset($this->_headers); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Part/TextPart.php b/lam/lib/3rdParty/composer/symfony/mime/Part/TextPart.php new file mode 100644 index 00000000..a41d91dd --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Part/TextPart.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part; + +use Symfony\Component\Mime\Encoder\Base64ContentEncoder; +use Symfony\Component\Mime\Encoder\ContentEncoderInterface; +use Symfony\Component\Mime\Encoder\EightBitContentEncoder; +use Symfony\Component\Mime\Encoder\QpContentEncoder; +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Header\Headers; + +/** + * @author Fabien Potencier + */ +class TextPart extends AbstractPart +{ + private static $encoders = []; + + private $body; + private $charset; + private $subtype; + private $disposition; + private $name; + private $encoding; + + /** + * @param resource|string $body + */ + public function __construct($body, ?string $charset = 'utf-8', $subtype = 'plain', string $encoding = null) + { + parent::__construct(); + + if (!\is_string($body) && !\is_resource($body)) { + throw new \TypeError(sprintf('The body of "%s" must be a string or a resource (got "%s").', self::class, \is_object($body) ? \get_class($body) : \gettype($body))); + } + + $this->body = $body; + $this->charset = $charset; + $this->subtype = $subtype; + + if (null === $encoding) { + $this->encoding = $this->chooseEncoding(); + } else { + if ('quoted-printable' !== $encoding && 'base64' !== $encoding && '8bit' !== $encoding) { + throw new InvalidArgumentException(sprintf('The encoding must be one of "quoted-printable", "base64", or "8bit" ("%s" given).', $encoding)); + } + $this->encoding = $encoding; + } + } + + public function getMediaType(): string + { + return 'text'; + } + + public function getMediaSubtype(): string + { + return $this->subtype; + } + + /** + * @param string $disposition one of attachment, inline, or form-data + * + * @return $this + */ + public function setDisposition(string $disposition) + { + $this->disposition = $disposition; + + return $this; + } + + /** + * Sets the name of the file (used by FormDataPart). + * + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + public function getBody(): string + { + if (!\is_resource($this->body)) { + return $this->body; + } + + if (stream_get_meta_data($this->body)['seekable'] ?? false) { + rewind($this->body); + } + + return stream_get_contents($this->body) ?: ''; + } + + public function bodyToString(): string + { + return $this->getEncoder()->encodeString($this->getBody(), $this->charset); + } + + public function bodyToIterable(): iterable + { + if (\is_resource($this->body)) { + if (stream_get_meta_data($this->body)['seekable'] ?? false) { + rewind($this->body); + } + yield from $this->getEncoder()->encodeByteStream($this->body); + } else { + yield $this->getEncoder()->encodeString($this->body); + } + } + + public function getPreparedHeaders(): Headers + { + $headers = parent::getPreparedHeaders(); + + $headers->setHeaderBody('Parameterized', 'Content-Type', $this->getMediaType().'/'.$this->getMediaSubtype()); + if ($this->charset) { + $headers->setHeaderParameter('Content-Type', 'charset', $this->charset); + } + if ($this->name) { + $headers->setHeaderParameter('Content-Type', 'name', $this->name); + } + $headers->setHeaderBody('Text', 'Content-Transfer-Encoding', $this->encoding); + + if (!$headers->has('Content-Disposition') && null !== $this->disposition) { + $headers->setHeaderBody('Parameterized', 'Content-Disposition', $this->disposition); + if ($this->name) { + $headers->setHeaderParameter('Content-Disposition', 'name', $this->name); + } + } + + return $headers; + } + + public function asDebugString(): string + { + $str = parent::asDebugString(); + if (null !== $this->charset) { + $str .= ' charset: '.$this->charset; + } + if (null !== $this->disposition) { + $str .= ' disposition: '.$this->disposition; + } + + return $str; + } + + private function getEncoder(): ContentEncoderInterface + { + if ('8bit' === $this->encoding) { + return self::$encoders[$this->encoding] ?? (self::$encoders[$this->encoding] = new EightBitContentEncoder()); + } + + if ('quoted-printable' === $this->encoding) { + return self::$encoders[$this->encoding] ?? (self::$encoders[$this->encoding] = new QpContentEncoder()); + } + + return self::$encoders[$this->encoding] ?? (self::$encoders[$this->encoding] = new Base64ContentEncoder()); + } + + private function chooseEncoding(): string + { + if (null === $this->charset) { + return 'base64'; + } + + return 'quoted-printable'; + } + + /** + * @return array + */ + public function __sleep() + { + // convert resources to strings for serialization + if (\is_resource($this->body)) { + $this->body = $this->getBody(); + } + + $this->_headers = $this->getHeaders(); + + return ['_headers', 'body', 'charset', 'subtype', 'disposition', 'name', 'encoding']; + } + + public function __wakeup() + { + $r = new \ReflectionProperty(AbstractPart::class, 'headers'); + $r->setAccessible(true); + $r->setValue($this, $this->_headers); + unset($this->_headers); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/README.md b/lam/lib/3rdParty/composer/symfony/mime/README.md new file mode 100644 index 00000000..4d565c92 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/README.md @@ -0,0 +1,13 @@ +MIME Component +============== + +The MIME component allows manipulating MIME messages. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/mime.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/lam/lib/3rdParty/composer/symfony/mime/RawMessage.php b/lam/lib/3rdParty/composer/symfony/mime/RawMessage.php new file mode 100644 index 00000000..79a27e9f --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/RawMessage.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Exception\LogicException; + +/** + * @author Fabien Potencier + */ +class RawMessage implements \Serializable +{ + private $message; + + /** + * @param iterable|string $message + */ + public function __construct($message) + { + $this->message = $message; + } + + public function toString(): string + { + if (\is_string($this->message)) { + return $this->message; + } + + return $this->message = implode('', iterator_to_array($this->message, false)); + } + + public function toIterable(): iterable + { + if (\is_string($this->message)) { + yield $this->message; + + return; + } + + $message = ''; + foreach ($this->message as $chunk) { + $message .= $chunk; + yield $chunk; + } + $this->message = $message; + } + + /** + * @throws LogicException if the message is not valid + */ + public function ensureValidity() + { + } + + /** + * @internal + */ + final public function serialize(): string + { + return serialize($this->__serialize()); + } + + /** + * @internal + */ + final public function unserialize($serialized) + { + $this->__unserialize(unserialize($serialized)); + } + + public function __serialize(): array + { + return [$this->message]; + } + + public function __unserialize(array $data): void + { + [$this->message] = $data; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Resources/bin/update_mime_types.php b/lam/lib/3rdParty/composer/symfony/mime/Resources/bin/update_mime_types.php new file mode 100644 index 00000000..74a9449c --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Resources/bin/update_mime_types.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +// load new map +$data = file_get_contents('https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types'); +$new = []; +foreach (explode("\n", $data) as $line) { + if (!$line || '#' == $line[0]) { + continue; + } + $mimeType = substr($line, 0, strpos($line, "\t")); + $extensions = explode(' ', substr($line, strrpos($line, "\t") + 1)); + $new[$mimeType] = $extensions; +} + +$xml = simplexml_load_string(file_get_contents('https://raw.github.com/minad/mimemagic/master/script/freedesktop.org.xml')); +foreach ($xml as $node) { + $exts = []; + foreach ($node->glob as $glob) { + $pattern = (string) $glob['pattern']; + if ('*' != $pattern[0] || '.' != $pattern[1]) { + continue; + } + + $exts[] = substr($pattern, 2); + } + + if (!$exts) { + continue; + } + + $mt = strtolower((string) $node['type']); + $new[$mt] = array_merge($new[$mt] ?? [], $exts); + foreach ($node->alias as $alias) { + $mt = strtolower((string) $alias['type']); + $new[$mt] = array_merge($new[$mt] ?? [], $exts); + } +} + +// load current map +$data = file_get_contents($output = __DIR__.'/../../MimeTypes.php'); +$current = []; +$pre = ''; +$post = ''; +foreach (explode("\n", $data) as $line) { + if (!preg_match("{^ '([^']+/[^']+)' => \['(.+)'\],$}", $line, $matches)) { + if (!$current) { + $pre .= $line."\n"; + } else { + $post .= $line."\n"; + } + continue; + } + $current[$matches[1]] = explode("', '", $matches[2]); +} + +// we merge the 2 maps (we never remove old mime types) +$map = array_replace_recursive($current, $new); +ksort($map); + +$data = $pre; +foreach ($map as $mimeType => $exts) { + $data .= sprintf(" '%s' => ['%s'],\n", $mimeType, implode("', '", array_unique($exts))); +} +$data .= $post; + +// reverse map +// we prefill the extensions with some preferences for content-types +$exts = [ + 'aif' => ['audio/x-aiff'], + 'aiff' => ['audio/x-aiff'], + 'aps' => ['application/postscript'], + 'avi' => ['video/avi'], + 'bmp' => ['image/bmp'], + 'bz2' => ['application/x-bz2'], + 'css' => ['text/css'], + 'csv' => ['text/csv'], + 'dmg' => ['application/x-apple-diskimage'], + 'doc' => ['application/msword'], + 'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'], + 'eml' => ['message/rfc822'], + 'exe' => ['application/x-ms-dos-executable'], + 'flv' => ['video/x-flv'], + 'gif' => ['image/gif'], + 'gz' => ['application/x-gzip'], + 'hqx' => ['application/stuffit'], + 'htm' => ['text/html'], + 'html' => ['text/html'], + 'jar' => ['application/x-java-archive'], + 'jpeg' => ['image/jpeg'], + 'jpg' => ['image/jpeg'], + 'js' => ['text/javascript'], + 'm3u' => ['audio/x-mpegurl'], + 'm4a' => ['audio/mp4'], + 'mdb' => ['application/x-msaccess'], + 'mid' => ['audio/midi'], + 'midi' => ['audio/midi'], + 'mov' => ['video/quicktime'], + 'mp3' => ['audio/mpeg'], + 'mp4' => ['video/mp4'], + 'mpeg' => ['video/mpeg'], + 'mpg' => ['video/mpeg'], + 'ogg' => ['audio/ogg'], + 'pdf' => ['application/pdf'], + 'php' => ['application/x-php'], + 'php3' => ['application/x-php'], + 'php4' => ['application/x-php'], + 'php5' => ['application/x-php'], + 'png' => ['image/png'], + 'ppt' => ['application/vnd.ms-powerpoint'], + 'pptx' => ['application/vnd.openxmlformats-officedocument.presentationml.presentation'], + 'ps' => ['application/postscript'], + 'rar' => ['application/x-rar-compressed'], + 'rtf' => ['application/rtf'], + 'sit' => ['application/x-stuffit'], + 'svg' => ['image/svg+xml'], + 'tar' => ['application/x-tar'], + 'tif' => ['image/tiff'], + 'tiff' => ['image/tiff'], + 'ttf' => ['application/x-font-truetype'], + 'txt' => ['text/plain'], + 'vcf' => ['text/x-vcard'], + 'wav' => ['audio/wav'], + 'wma' => ['audio/x-ms-wma'], + 'wmv' => ['audio/x-ms-wmv'], + 'xls' => ['application/vnd.ms-excel'], + 'xlsx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'], + 'xml' => ['application/xml'], + 'zip' => ['application/zip'], +]; +foreach ($map as $mimeType => $extensions) { + foreach ($extensions as $extension) { + $exts[$extension][] = $mimeType; + } +} +ksort($exts); + +$updated = ''; +$state = 0; +foreach (explode("\n", $data) as $line) { + if (!preg_match("{^ '([^'/]+)' => \['(.+)'\],$}", $line, $matches)) { + if (1 === $state) { + $state = 2; + foreach ($exts as $ext => $mimeTypes) { + $updated .= sprintf(" '%s' => ['%s'],\n", $ext, implode("', '", array_unique($mimeTypes))); + } + } + $updated .= $line."\n"; + continue; + } + $state = 1; +} + +$updated = preg_replace('{Updated from upstream on .+?\.}', 'Updated from upstream on '.date('Y-m-d'), $updated, -1); + +file_put_contents($output, rtrim($updated, "\n")."\n"); + +echo "Done.\n"; diff --git a/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailAddressContains.php b/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailAddressContains.php new file mode 100644 index 00000000..58ef360c --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailAddressContains.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\Header\MailboxHeader; +use Symfony\Component\Mime\Header\MailboxListHeader; +use Symfony\Component\Mime\RawMessage; + +final class EmailAddressContains extends Constraint +{ + private $headerName; + private $expectedValue; + + public function __construct(string $headerName, string $expectedValue) + { + $this->headerName = $headerName; + $this->expectedValue = $expectedValue; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('contains address "%s" with value "%s"', $this->headerName, $this->expectedValue); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function matches($message): bool + { + if (RawMessage::class === \get_class($message)) { + throw new \LogicException('Unable to test a message address on a RawMessage instance.'); + } + + $header = $message->getHeaders()->get($this->headerName); + if ($header instanceof MailboxHeader) { + return $this->expectedValue === $header->Address()->getAddress(); + } elseif ($header instanceof MailboxListHeader) { + foreach ($header->getAddresses() as $address) { + if ($this->expectedValue === $address->getAddress()) { + return true; + } + } + + return false; + } + + throw new \LogicException(sprintf('Unable to test a message address on a non-address header.')); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function failureDescription($message): string + { + return sprintf('the Email %s (value is %s)', $this->toString(), $message->getHeaders()->get($this->headerName)->getBodyAsString()); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailAttachmentCount.php b/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailAttachmentCount.php new file mode 100644 index 00000000..b219f28b --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailAttachmentCount.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\RawMessage; + +final class EmailAttachmentCount extends Constraint +{ + private $expectedValue; + private $transport; + + public function __construct(int $expectedValue, string $transport = null) + { + $this->expectedValue = $expectedValue; + $this->transport = $transport; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('has sent "%d" attachment(s)', $this->expectedValue); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function matches($message): bool + { + if (RawMessage::class === \get_class($message) || Message::class === \get_class($message)) { + throw new \LogicException('Unable to test a message attachment on a RawMessage or Message instance.'); + } + + return $this->expectedValue === \count($message->getAttachments()); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function failureDescription($message): string + { + return 'the Email '.$this->toString(); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailHasHeader.php b/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailHasHeader.php new file mode 100644 index 00000000..a29f835f --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailHasHeader.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\RawMessage; + +final class EmailHasHeader extends Constraint +{ + private $headerName; + + public function __construct(string $headerName) + { + $this->headerName = $headerName; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('has header "%s"', $this->headerName); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function matches($message): bool + { + if (RawMessage::class === \get_class($message)) { + throw new \LogicException('Unable to test a message header on a RawMessage instance.'); + } + + return $message->getHeaders()->has($this->headerName); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function failureDescription($message): string + { + return 'the Email '.$this->toString(); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailHeaderSame.php b/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailHeaderSame.php new file mode 100644 index 00000000..bc7e330e --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailHeaderSame.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\RawMessage; + +final class EmailHeaderSame extends Constraint +{ + private $headerName; + private $expectedValue; + + public function __construct(string $headerName, string $expectedValue) + { + $this->headerName = $headerName; + $this->expectedValue = $expectedValue; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function matches($message): bool + { + if (RawMessage::class === \get_class($message)) { + throw new \LogicException('Unable to test a message header on a RawMessage instance.'); + } + + return $this->expectedValue === $message->getHeaders()->get($this->headerName)->getBodyAsString(); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function failureDescription($message): string + { + return sprintf('the Email %s (value is %s)', $this->toString(), $message->getHeaders()->get($this->headerName)->getBodyAsString()); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php b/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php new file mode 100644 index 00000000..89651951 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; + +final class EmailHtmlBodyContains extends Constraint +{ + private $expectedText; + + public function __construct(string $expectedText) + { + $this->expectedText = $expectedText; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('contains "%s"', $this->expectedText); + } + + /** + * {@inheritdoc} + * + * @param RawMessage $message + */ + protected function matches($message): bool + { + if (RawMessage::class === \get_class($message) || Message::class === \get_class($message)) { + throw new \LogicException('Unable to test a message HTML body on a RawMessage or Message instance.'); + } + + return false !== mb_strpos($message->getHtmlBody(), $this->expectedText); + } + + /** + * {@inheritdoc} + * + * @param RawMessage $message + */ + protected function failureDescription($message): string + { + return 'the Email HTML body '.$this->toString(); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailTextBodyContains.php b/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailTextBodyContains.php new file mode 100644 index 00000000..b5e87f96 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/Test/Constraint/EmailTextBodyContains.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; + +final class EmailTextBodyContains extends Constraint +{ + private $expectedText; + + public function __construct(string $expectedText) + { + $this->expectedText = $expectedText; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('contains "%s"', $this->expectedText); + } + + /** + * {@inheritdoc} + * + * @param RawMessage $message + */ + protected function matches($message): bool + { + if (RawMessage::class === \get_class($message) || Message::class === \get_class($message)) { + throw new \LogicException('Unable to test a message text body on a RawMessage or Message instance.'); + } + + return false !== mb_strpos($message->getTextBody(), $this->expectedText); + } + + /** + * {@inheritdoc} + * + * @param RawMessage $message + */ + protected function failureDescription($message): string + { + return 'the Email text body '.$this->toString(); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/mime/composer.json b/lam/lib/3rdParty/composer/symfony/mime/composer.json new file mode 100644 index 00000000..c3f26261 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/mime/composer.json @@ -0,0 +1,42 @@ +{ + "name": "symfony/mime", + "type": "library", + "description": "A library to manipulate MIME messages", + "keywords": ["mime", "mime-type"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^7.2.5", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10", + "symfony/dependency-injection": "^4.4|^5.0" + }, + "conflict": { + "symfony/mailer": "<4.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Mime\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-ctype/composer.json b/lam/lib/3rdParty/composer/symfony/polyfill-ctype/composer.json index 090f923e..2a2ea044 100644 --- a/lam/lib/3rdParty/composer/symfony/polyfill-ctype/composer.json +++ b/lam/lib/3rdParty/composer/symfony/polyfill-ctype/composer.json @@ -28,7 +28,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.13-dev" } } } diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/Idn.php b/lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/Idn.php new file mode 100644 index 00000000..adb718d2 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/Idn.php @@ -0,0 +1,283 @@ + + * @author Sebastian Kroczek + * @author Dmitry Lukashin + * @author Laurent Bassin + * + * @internal + */ +final class Idn +{ + const INTL_IDNA_VARIANT_2003 = 0; + const INTL_IDNA_VARIANT_UTS46 = 1; + + private static $encodeTable = array( + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + ); + + private static $decodeTable = array( + 'a' => 0, 'b' => 1, 'c' => 2, 'd' => 3, 'e' => 4, 'f' => 5, + 'g' => 6, 'h' => 7, 'i' => 8, 'j' => 9, 'k' => 10, 'l' => 11, + 'm' => 12, 'n' => 13, 'o' => 14, 'p' => 15, 'q' => 16, 'r' => 17, + 's' => 18, 't' => 19, 'u' => 20, 'v' => 21, 'w' => 22, 'x' => 23, + 'y' => 24, 'z' => 25, '0' => 26, '1' => 27, '2' => 28, '3' => 29, + '4' => 30, '5' => 31, '6' => 32, '7' => 33, '8' => 34, '9' => 35, + ); + + public static function idn_to_ascii($domain, $options, $variant, &$idna_info = array()) + { + if (\PHP_VERSION_ID >= 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) { + @trigger_error('idn_to_ascii(): INTL_IDNA_VARIANT_2003 is deprecated', E_USER_DEPRECATED); + } + + if (self::INTL_IDNA_VARIANT_UTS46 === $variant) { + $domain = mb_strtolower($domain, 'utf-8'); + } + + $parts = explode('.', $domain); + + foreach ($parts as $i => &$part) { + if ('' === $part && \count($parts) > 1 + $i) { + return false; + } + if (false === $part = self::encodePart($part)) { + return false; + } + } + + $output = implode('.', $parts); + + $idna_info = array( + 'result' => \strlen($output) > 255 ? false : $output, + 'isTransitionalDifferent' => false, + 'errors' => 0, + ); + + return $idna_info['result']; + } + + public static function idn_to_utf8($domain, $options, $variant, &$idna_info = array()) + { + if (\PHP_VERSION_ID >= 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) { + @trigger_error('idn_to_utf8(): INTL_IDNA_VARIANT_2003 is deprecated', E_USER_DEPRECATED); + } + + $parts = explode('.', $domain); + + foreach ($parts as &$part) { + $length = \strlen($part); + if ($length < 1 || 63 < $length) { + continue; + } + if (0 !== strpos($part, 'xn--')) { + continue; + } + + $part = substr($part, 4); + $part = self::decodePart($part); + } + + $output = implode('.', $parts); + + $idna_info = array( + 'result' => \strlen($output) > 255 ? false : $output, + 'isTransitionalDifferent' => false, + 'errors' => 0, + ); + + return $idna_info['result']; + } + + private static function encodePart($input) + { + $codePoints = self::listCodePoints($input); + + $n = 128; + $bias = 72; + $delta = 0; + $h = $b = \count($codePoints['basic']); + + $output = ''; + foreach ($codePoints['basic'] as $code) { + $output .= mb_chr($code, 'utf-8'); + } + if ($input === $output) { + return $output; + } + if ($b > 0) { + $output .= '-'; + } + + $codePoints['nonBasic'] = array_unique($codePoints['nonBasic']); + sort($codePoints['nonBasic']); + + $i = 0; + $length = mb_strlen($input, 'utf-8'); + while ($h < $length) { + $m = $codePoints['nonBasic'][$i++]; + $delta += ($m - $n) * ($h + 1); + $n = $m; + + foreach ($codePoints['all'] as $c) { + if ($c < $n || $c < 128) { + ++$delta; + } + if ($c === $n) { + $q = $delta; + for ($k = 36;; $k += 36) { + $t = self::calculateThreshold($k, $bias); + if ($q < $t) { + break; + } + + $code = $t + (($q - $t) % (36 - $t)); + $output .= self::$encodeTable[$code]; + + $q = ($q - $t) / (36 - $t); + } + + $output .= self::$encodeTable[$q]; + $bias = self::adapt($delta, $h + 1, ($h === $b)); + $delta = 0; + ++$h; + } + } + + ++$delta; + ++$n; + } + + $output = 'xn--'.$output; + + return \strlen($output) < 1 || 63 < \strlen($output) ? false : strtolower($output); + } + + private static function listCodePoints($input) + { + $codePoints = array( + 'all' => array(), + 'basic' => array(), + 'nonBasic' => array(), + ); + + $length = mb_strlen($input, 'utf-8'); + for ($i = 0; $i < $length; ++$i) { + $char = mb_substr($input, $i, 1, 'utf-8'); + $code = mb_ord($char, 'utf-8'); + if ($code < 128) { + $codePoints['all'][] = $codePoints['basic'][] = $code; + } else { + $codePoints['all'][] = $codePoints['nonBasic'][] = $code; + } + } + + return $codePoints; + } + + private static function calculateThreshold($k, $bias) + { + if ($k <= $bias + 1) { + return 1; + } + if ($k >= $bias + 26) { + return 26; + } + + return $k - $bias; + } + + private static function adapt($delta, $numPoints, $firstTime) + { + $delta = (int) ($firstTime ? $delta / 700 : $delta / 2); + $delta += (int) ($delta / $numPoints); + + $k = 0; + while ($delta > 35 * 13) { + $delta = (int) ($delta / 35); + $k = $k + 36; + } + + return $k + (int) (36 * $delta / ($delta + 38)); + } + + private static function decodePart($input) + { + $n = 128; + $i = 0; + $bias = 72; + $output = ''; + + $pos = strrpos($input, '-'); + if (false !== $pos) { + $output = substr($input, 0, $pos++); + } else { + $pos = 0; + } + + $outputLength = \strlen($output); + $inputLength = \strlen($input); + + while ($pos < $inputLength) { + $oldi = $i; + $w = 1; + + for ($k = 36;; $k += 36) { + $digit = self::$decodeTable[$input[$pos++]]; + $i += $digit * $w; + $t = self::calculateThreshold($k, $bias); + + if ($digit < $t) { + break; + } + + $w *= 36 - $t; + } + + $bias = self::adapt($i - $oldi, ++$outputLength, 0 === $oldi); + $n = $n + (int) ($i / $outputLength); + $i = $i % $outputLength; + $output = mb_substr($output, 0, $i, 'utf-8').mb_chr($n, 'utf-8').mb_substr($output, $i, $outputLength - 1, 'utf-8'); + + ++$i; + } + + return $output; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/LICENSE b/lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/LICENSE new file mode 100644 index 00000000..3f853aaf --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/README.md b/lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/README.md new file mode 100644 index 00000000..5fd8c6e9 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/README.md @@ -0,0 +1,12 @@ +Symfony Polyfill / Intl: Idn +============================ + +This component provides `idn_to_ascii` and `idn_to_utf8` functions to users who run php versions without the intl extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/bootstrap.php b/lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/bootstrap.php new file mode 100644 index 00000000..c6e3921d --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/bootstrap.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Idn as p; + +if (!function_exists('idn_to_ascii')) { + define('U_IDNA_PROHIBITED_ERROR', 66560); + define('U_IDNA_ERROR_START', 66560); + define('U_IDNA_UNASSIGNED_ERROR', 66561); + define('U_IDNA_CHECK_BIDI_ERROR', 66562); + define('U_IDNA_STD3_ASCII_RULES_ERROR', 66563); + define('U_IDNA_ACE_PREFIX_ERROR', 66564); + define('U_IDNA_VERIFICATION_ERROR', 66565); + define('U_IDNA_LABEL_TOO_LONG_ERROR', 66566); + define('U_IDNA_ZERO_LENGTH_LABEL_ERROR', 66567); + define('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR', 66568); + define('U_IDNA_ERROR_LIMIT', 66569); + define('U_STRINGPREP_PROHIBITED_ERROR', 66560); + define('U_STRINGPREP_UNASSIGNED_ERROR', 66561); + define('U_STRINGPREP_CHECK_BIDI_ERROR', 66562); + define('IDNA_DEFAULT', 0); + define('IDNA_ALLOW_UNASSIGNED', 1); + define('IDNA_USE_STD3_RULES', 2); + define('IDNA_CHECK_BIDI', 4); + define('IDNA_CHECK_CONTEXTJ', 8); + define('IDNA_NONTRANSITIONAL_TO_ASCII', 16); + define('IDNA_NONTRANSITIONAL_TO_UNICODE', 32); + define('INTL_IDNA_VARIANT_2003', 0); + define('INTL_IDNA_VARIANT_UTS46', 1); + define('IDNA_ERROR_EMPTY_LABEL', 1); + define('IDNA_ERROR_LABEL_TOO_LONG', 2); + define('IDNA_ERROR_DOMAIN_NAME_TOO_LONG', 4); + define('IDNA_ERROR_LEADING_HYPHEN', 8); + define('IDNA_ERROR_TRAILING_HYPHEN', 16); + define('IDNA_ERROR_HYPHEN_3_4', 32); + define('IDNA_ERROR_LEADING_COMBINING_MARK', 64); + define('IDNA_ERROR_DISALLOWED', 128); + define('IDNA_ERROR_PUNYCODE', 256); + define('IDNA_ERROR_LABEL_HAS_DOT', 512); + define('IDNA_ERROR_INVALID_ACE_LABEL', 1024); + define('IDNA_ERROR_BIDI', 2048); + define('IDNA_ERROR_CONTEXTJ', 4096); + + if (PHP_VERSION_ID < 70400) { + function idn_to_ascii($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_2003, &$idna_info = array()) { return p\Idn::idn_to_ascii($domain, $options, $variant, $idna_info); } + function idn_to_utf8($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_2003, &$idna_info = array()) { return p\Idn::idn_to_utf8($domain, $options, $variant, $idna_info); } + } else { + function idn_to_ascii($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = array()) { return p\Idn::idn_to_ascii($domain, $options, $variant, $idna_info); } + function idn_to_utf8($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = array()) { return p\Idn::idn_to_utf8($domain, $options, $variant, $idna_info); } + } +} diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/composer.json b/lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/composer.json new file mode 100644 index 00000000..428c13e6 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-intl-idn/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/polyfill-intl-idn", + "type": "library", + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "idn"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php72": "^1.9" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Intl\\Idn\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-intl": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/LICENSE b/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 00000000..4cd8bdd3 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/Mbstring.php b/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 00000000..bf882ba2 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,840 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + const MB_CASE_FOLD = PHP_INT_MAX; + + private static $encodingList = array('ASCII', 'UTF-8'); + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + private static $caseFold = array( + array('µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"), + array('μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'), + ); + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) + { + $vars = array(&$a, &$b, &$c, &$d, &$e, &$f); + + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || !$convmap) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return Mbstring::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || !$convmap) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !\is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, array(__CLASS__, 'title_case'), $s); + } else { + if (MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) { + self::$internalEncoding = $encoding; + + return true; + } + + return false; + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($lang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $lang; + + return true; + } + + return false; + } + + public static function mb_list_encodings() + { + return array('UTF-8'); + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return array('utf8'); + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var); + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING); + + return false; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = iconv_strrpos($haystack, $needle, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !\is_scalar($string) && !(\is_object($string) && \method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + trigger_error('The length of each segment must be greater than zero', E_USER_WARNING); + + return false; + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + return preg_split("/(.{{$split_length}})/u", $string, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + } + + $result = array(); + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (0 === strcasecmp($c, 'none')) { + return true; + } + + return null !== $c ? false : 'none'; + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrchr($haystack, $needle, $part); + } + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = array( + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ); + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/README.md b/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/README.md new file mode 100644 index 00000000..342e8286 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](http://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 00000000..e6fbfa64 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1096 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', +); diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 00000000..2a8f6e73 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ᾈ', + 'ᾁ' => 'ᾉ', + 'ᾂ' => 'ᾊ', + 'ᾃ' => 'ᾋ', + 'ᾄ' => 'ᾌ', + 'ᾅ' => 'ᾍ', + 'ᾆ' => 'ᾎ', + 'ᾇ' => 'ᾏ', + 'ᾐ' => 'ᾘ', + 'ᾑ' => 'ᾙ', + 'ᾒ' => 'ᾚ', + 'ᾓ' => 'ᾛ', + 'ᾔ' => 'ᾜ', + 'ᾕ' => 'ᾝ', + 'ᾖ' => 'ᾞ', + 'ᾗ' => 'ᾟ', + 'ᾠ' => 'ᾨ', + 'ᾡ' => 'ᾩ', + 'ᾢ' => 'ᾪ', + 'ᾣ' => 'ᾫ', + 'ᾤ' => 'ᾬ', + 'ᾥ' => 'ᾭ', + 'ᾦ' => 'ᾮ', + 'ᾧ' => 'ᾯ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ᾼ', + 'ι' => 'Ι', + 'ῃ' => 'ῌ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ῼ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', +); diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/bootstrap.php b/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 00000000..204a41ba --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_strlen')) { + define('MB_CASE_UPPER', 0); + define('MB_CASE_LOWER', 1); + define('MB_CASE_TITLE', 2); + + function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); } + function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); } + function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); } + function mb_decode_numericentity($s, $convmap, $enc = null) { return p\Mbstring::mb_decode_numericentity($s, $convmap, $enc); } + function mb_encode_numericentity($s, $convmap, $enc = null, $is_hex = false) { return p\Mbstring::mb_encode_numericentity($s, $convmap, $enc, $is_hex); } + function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); } + function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); } + function mb_language($lang = null) { return p\Mbstring::mb_language($lang); } + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } + function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); } + function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); } + function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); } + function mb_parse_str($s, &$result = array()) { parse_str($s, $result); } + function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); } + function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); } + function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); } + function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); } + function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); } + function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); } + function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); } + function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); } + function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); } + function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); } + function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); } + function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); } + function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); } + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } + function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); } + function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); } + function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); } + function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); } + function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); } + function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); } +} +if (!function_exists('mb_chr')) { + function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); } + function mb_chr($code, $enc = null) { return p\Mbstring::mb_chr($code, $enc); } + function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); } +} + +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $split_length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $split_length, $encoding); } +} diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/composer.json b/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/composer.json new file mode 100644 index 00000000..c9680743 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-php72/LICENSE b/lam/lib/3rdParty/composer/symfony/polyfill-php72/LICENSE new file mode 100644 index 00000000..4cd8bdd3 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-php72/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-php72/Php72.php b/lam/lib/3rdParty/composer/symfony/polyfill-php72/Php72.php new file mode 100644 index 00000000..d531e844 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-php72/Php72.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php72; + +/** + * @author Nicolas Grekas + * @author Dariusz Rumiński + * + * @internal + */ +final class Php72 +{ + private static $hashMask; + + public static function utf8_encode($s) + { + $s .= $s; + $len = \strlen($s); + + for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) { + switch (true) { + case $s[$i] < "\x80": $s[$j] = $s[$i]; break; + case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break; + default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break; + } + } + + return substr($s, 0, $j); + } + + public static function utf8_decode($s) + { + $s = (string) $s; + $len = \strlen($s); + + for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) { + switch ($s[$i] & "\xF0") { + case "\xC0": + case "\xD0": + $c = (\ord($s[$i] & "\x1F") << 6) | \ord($s[++$i] & "\x3F"); + $s[$j] = $c < 256 ? \chr($c) : '?'; + break; + + case "\xF0": + ++$i; + // no break + + case "\xE0": + $s[$j] = '?'; + $i += 2; + break; + + default: + $s[$j] = $s[$i]; + } + } + + return substr($s, 0, $j); + } + + public static function php_os_family() + { + if ('\\' === \DIRECTORY_SEPARATOR) { + return 'Windows'; + } + + $map = array( + 'Darwin' => 'Darwin', + 'DragonFly' => 'BSD', + 'FreeBSD' => 'BSD', + 'NetBSD' => 'BSD', + 'OpenBSD' => 'BSD', + 'Linux' => 'Linux', + 'SunOS' => 'Solaris', + ); + + return isset($map[PHP_OS]) ? $map[PHP_OS] : 'Unknown'; + } + + public static function spl_object_id($object) + { + if (null === self::$hashMask) { + self::initHashMask(); + } + if (null === $hash = spl_object_hash($object)) { + return; + } + + return self::$hashMask ^ hexdec(substr($hash, 16 - \PHP_INT_SIZE, \PHP_INT_SIZE)); + } + + public static function sapi_windows_vt100_support($stream, $enable = null) + { + if (!\is_resource($stream)) { + trigger_error('sapi_windows_vt100_support() expects parameter 1 to be resource, '.\gettype($stream).' given', E_USER_WARNING); + + return false; + } + + $meta = stream_get_meta_data($stream); + + if ('STDIO' !== $meta['stream_type']) { + trigger_error('sapi_windows_vt100_support() was not able to analyze the specified stream', E_USER_WARNING); + + return false; + } + + // We cannot actually disable vt100 support if it is set + if (false === $enable || !self::stream_isatty($stream)) { + return false; + } + + // The native function does not apply to stdin + $meta = array_map('strtolower', $meta); + $stdin = 'php://stdin' === $meta['uri'] || 'php://fd/0' === $meta['uri']; + + return !$stdin + && (false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM') + || 'Hyper' === getenv('TERM_PROGRAM')); + } + + public static function stream_isatty($stream) + { + if (!\is_resource($stream)) { + trigger_error('stream_isatty() expects parameter 1 to be resource, '.\gettype($stream).' given', E_USER_WARNING); + + return false; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $stat = @fstat($stream); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + } + + return \function_exists('posix_isatty') && @posix_isatty($stream); + } + + private static function initHashMask() + { + $obj = (object) array(); + self::$hashMask = -1; + + // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below + $obFuncs = array('ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush'); + foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) { + if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && \in_array($frame['function'], $obFuncs)) { + $frame['line'] = 0; + break; + } + } + if (!empty($frame['line'])) { + ob_start(); + debug_zval_dump($obj); + self::$hashMask = (int) substr(ob_get_clean(), 17); + } + + self::$hashMask ^= hexdec(substr(spl_object_hash($obj), 16 - \PHP_INT_SIZE, \PHP_INT_SIZE)); + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if (null == $encoding) { + $s = mb_convert_encoding($s, 'UTF-8'); + } elseif ('UTF-8' !== $encoding) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-php72/README.md b/lam/lib/3rdParty/composer/symfony/polyfill-php72/README.md new file mode 100644 index 00000000..82c45f7a --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-php72/README.md @@ -0,0 +1,27 @@ +Symfony Polyfill / Php72 +======================== + +This component provides functions added to PHP 7.2 core: + +- [`spl_object_id`](https://php.net/spl_object_id) +- [`stream_isatty`](https://php.net/stream_isatty) + +On Windows only: + +- [`sapi_windows_vt100_support`](https://php.net/sapi_windows_vt100_support) + +Moved to core since 7.2 (was in the optional XML extension earlier): + +- [`utf8_encode`](https://php.net/utf8_encode) +- [`utf8_decode`](https://php.net/utf8_decode) + +Also, it provides a constant added to PHP 7.2: +- [`PHP_OS_FAMILY`](http://php.net/manual/en/reserved.constants.php#constant.php-os-family) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-php72/bootstrap.php b/lam/lib/3rdParty/composer/symfony/polyfill-php72/bootstrap.php new file mode 100644 index 00000000..519056de --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-php72/bootstrap.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php72 as p; + +if (PHP_VERSION_ID < 70200) { + if ('\\' === DIRECTORY_SEPARATOR && !function_exists('sapi_windows_vt100_support')) { + function sapi_windows_vt100_support($stream, $enable = null) { return p\Php72::sapi_windows_vt100_support($stream, $enable); } + } + if (!function_exists('stream_isatty')) { + function stream_isatty($stream) { return p\Php72::stream_isatty($stream); } + } + if (!function_exists('utf8_encode')) { + function utf8_encode($s) { return p\Php72::utf8_encode($s); } + function utf8_decode($s) { return p\Php72::utf8_decode($s); } + } + if (!function_exists('spl_object_id')) { + function spl_object_id($s) { return p\Php72::spl_object_id($s); } + } + if (!defined('PHP_OS_FAMILY')) { + define('PHP_OS_FAMILY', p\Php72::php_os_family()); + } + if (!function_exists('mb_chr')) { + function mb_ord($s, $enc = null) { return p\Php72::mb_ord($s, $enc); } + function mb_chr($code, $enc = null) { return p\Php72::mb_chr($code, $enc); } + function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); } + } +} diff --git a/lam/lib/3rdParty/composer/symfony/polyfill-php72/composer.json b/lam/lib/3rdParty/composer/symfony/polyfill-php72/composer.json new file mode 100644 index 00000000..0d37167f --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/polyfill-php72/composer.json @@ -0,0 +1,31 @@ +{ + "name": "symfony/polyfill-php72", + "type": "library", + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php72\\": "" }, + "files": [ "bootstrap.php" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + } +} diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index 364bdae1..4bbd51f2 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -15,17 +15,23 @@ use Cose\Algorithm\Signature\RSA\RS256; use Cose\Algorithm\Signature\RSA\RS384; use Cose\Algorithm\Signature\RSA\RS512; use \Cose\Algorithms; -use http\Client; +use Symfony\Component\HttpFoundation\Request; use Webauthn\AttestationStatement\AndroidKeyAttestationStatementSupport; -use Webauthn\AttestationStatement\AndroidSafetyNetAttestationStatementSupport; +use Webauthn\AttestationStatement\AttestationObjectLoader; use Webauthn\AttestationStatement\AttestationStatementSupportManager; use Webauthn\AttestationStatement\FidoU2FAttestationStatementSupport; use Webauthn\AttestationStatement\NoneAttestationStatementSupport; use Webauthn\AttestationStatement\PackedAttestationStatementSupport; use Webauthn\AttestationStatement\TPMAttestationStatementSupport; +use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler; +use Webauthn\AuthenticatorAttestationResponse; +use Webauthn\AuthenticatorAttestationResponseValidator; use \Webauthn\PublicKeyCredentialCreationOptions; +use Webauthn\PublicKeyCredentialLoader; use \Webauthn\PublicKeyCredentialRpEntity; use \Webauthn\PublicKeyCredentialParameters; +use Webauthn\PublicKeyCredentialSource; +use Webauthn\PublicKeyCredentialSourceRepository; use \Webauthn\PublicKeyCredentialUserEntity; use \Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; use \Webauthn\AuthenticatorSelectionCriteria; @@ -145,13 +151,34 @@ function getCredentialParameters() { * Verifies the registration and stores it in the database. * * @param PublicKeyCredentialCreationOptions $registration registration object - * @param $clientResponse client response + * @param string $clientResponse client response * @return bool true if response is valid and registration succeeded */ function storeNewRegistration($registration, $clientResponse) { $decoder = getCborDecoder(); $tokenBindingHandler = new IgnoreTokenBindingHandler(); $attestationSupportManager = getAttestationSupportManager($decoder); + $attestationObjectLoader = getAttestationObjectLoader($attestationSupportManager, $decoder); + $publicKeyCredentialLoader = getPublicKeyCredentialLoader($attestationObjectLoader, $decoder); + $extensionOutputCheckerHandler = getExtensionOutputChecker(); + $repository = new PublicKeyCredentialSourceRepositorySQLite(); + $responseValidator = new AuthenticatorAttestationResponseValidator( + $attestationSupportManager, $repository, $tokenBindingHandler, $extensionOutputCheckerHandler); + try { + logNewMessage(LOG_ERR, 'RESPONSE: ' . $clientResponse); + $publicKeyCredential = $publicKeyCredentialLoader->load($clientResponse); + $authenticatorAttestationResponse = $publicKeyCredential->getResponse(); + if (!$authenticatorAttestationResponse instanceof AuthenticatorAttestationResponse) { + logNewMessage(LOG_ERR, 'Invalid webauthn response: ' . $clientResponse); + return false; + } + $symfonyRequest = Request::createFromGlobals(); + $responseValidator->check($authenticatorAttestationResponse, $registration, $symfonyRequest); + return true; + } + catch (\Throwable $exception) { + logNewMessage(LOG_ERR, 'Webauthn validation failed: ' . $exception->getMessage() . $exception->getTraceAsString()); + } return false; } @@ -174,7 +201,6 @@ function getAttestationSupportManager($decoder) { $manager = new AttestationStatementSupportManager(); $manager->add(new NoneAttestationStatementSupport()); $manager->add(new FidoU2FAttestationStatementSupport()); - $manager->add(new AndroidSafetyNetAttestationStatementSupport(new Client() /*TODO*/)); $manager->add(new AndroidKeyAttestationStatementSupport($decoder)); $manager->add(new TPMAttestationStatementSupport()); $coseManager = new Manager(); @@ -189,3 +215,80 @@ function getAttestationSupportManager($decoder) { $manager->add(new PackedAttestationStatementSupport($decoder, $coseManager)); return $manager; } + +/** + * Returns the attestation object loader. + * + * @param AttestationStatementSupportManager $manager support manager + * @param Decoder $decoder decoder + * @return AttestationObjectLoader attestation object loader + */ +function getAttestationObjectLoader($manager, $decoder) { + return new AttestationObjectLoader($manager, $decoder); +} + +/** + * Creates the public key credential loader. + * + * @param AttestationObjectLoader $attestationObjectLoader attestation object loader + * @param Decoder $decoder decoder + * @return PublicKeyCredentialLoader public key credential loader + */ +function getPublicKeyCredentialLoader($attestationObjectLoader, $decoder) { + return new PublicKeyCredentialLoader($attestationObjectLoader, $decoder); +} + +/** + * Returns the extension output checker handler. + * No extensions are checked at this time. + * + * @return ExtensionOutputCheckerHandler handler + */ +function getExtensionOutputChecker() { + return new ExtensionOutputCheckerHandler(); +} + +/** + * Stores the public key credentials in the SQLite database. + * + * @package LAM\LOGIN\WEBAUTHN + */ +class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSourceRepository { + + /** + * Finds the public key for the given credential id. + * + * @param string $publicKeyCredentialId credential id + * @return PublicKeyCredentialSource|null credential source + */ + public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource { + // TODO: Implement findOneByCredentialId() method. + logNewMessage(LOG_WARNING, 'FIND ONE: ' . $publicKeyCredentialId); + return null; + } + + /** + * Finds all credential entries for the given user. + * + * @param PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity credential user entity + * @return PublicKeyCredentialSource[] credential sources + */ + public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array { + // TODO: Implement findAllForUserEntity() method. + logNewMessage(LOG_WARNING, 'FIND ALL: ' . json_encode($publicKeyCredentialUserEntity)); + return array(); + } + + /** + * Saves the given credential in the database. + * + * @param PublicKeyCredentialSource $publicKeyCredentialSource credential + */ + public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void { + // TODO: Implement saveCredentialSource() method. + logNewMessage(LOG_WARNING, 'SAVE: ' . json_encode($publicKeyCredentialSource)); + } + +} + + diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index 9233031a..32ae92d4 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -1418,7 +1418,7 @@ window.lam.webauthn.register = function(publicKey) { let publicKeyCredential = { id: data.id, type: data.type, - rawId: btoa(String.fromCharCode(new Uint8Array(data.rawId))), + rawId: window.lam.webauthn.arrayToBase64String(new Uint8Array(data.rawId)), response: { clientDataJSON: window.lam.webauthn.arrayToBase64String(new Uint8Array(data.response.clientDataJSON)), attestationObject: window.lam.webauthn.arrayToBase64String(new Uint8Array(data.response.attestationObject)) From e4363b83c4deaf1418a361c55c0906a40bd737d8 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 30 Nov 2019 10:42:08 +0100 Subject: [PATCH 13/51] webauthn --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bc10cdd3..e97bb4eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ addons: sonarcloud: organization: "ldap-account-manager" php: - - '5.6' + - '7.2' cache: directories: From 41b486905275a6b131b6220449d4a7cd80eafda0 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 30 Nov 2019 11:04:29 +0100 Subject: [PATCH 14/51] php 7.3 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e97bb4eb..cf1ad1a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ addons: sonarcloud: organization: "ldap-account-manager" php: - - '7.2' + - '7.3' cache: directories: From c71b01a73e3cf3ec9a2d13b14ed1ae8719ceb200 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 30 Nov 2019 11:12:07 +0100 Subject: [PATCH 15/51] php 7.3 --- lam/tests/lib/LAMConfigTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lam/tests/lib/LAMConfigTest.php b/lam/tests/lib/LAMConfigTest.php index f41da59f..2d887992 100644 --- a/lam/tests/lib/LAMConfigTest.php +++ b/lam/tests/lib/LAMConfigTest.php @@ -41,7 +41,7 @@ class LAMConfigTest extends TestCase { /** * Prepares the environment before running a test. */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); testCreateDefaultConfig(); $profiles = getConfigProfiles(); From 84d20e204a02069288524a3cbfb934a6b55df8b4 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 30 Nov 2019 11:16:25 +0100 Subject: [PATCH 16/51] php 7.3 --- lam/tests/lib/LAMConfigTest.php | 2 +- lam/tests/lib/baseModuleTest.php | 2 +- lam/tests/lib/modules/ppolicyUserTest.php | 2 +- lam/tests/lib/modules/shadowAccountTest.php | 2 +- lam/tests/lib/securityTest.php | 4 ++-- lam/tests/lib/typesTest.php | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lam/tests/lib/LAMConfigTest.php b/lam/tests/lib/LAMConfigTest.php index 2d887992..972c5403 100644 --- a/lam/tests/lib/LAMConfigTest.php +++ b/lam/tests/lib/LAMConfigTest.php @@ -57,7 +57,7 @@ class LAMConfigTest extends TestCase { /** * Cleans up the environment after running a test. */ - protected function tearDown() { + protected function tearDown(): void { $this->lAMConfig = null; deleteConfigProfile(LAMConfigTest::FILE_NAME); $profiles = getConfigProfiles(); diff --git a/lam/tests/lib/baseModuleTest.php b/lam/tests/lib/baseModuleTest.php index d07390ea..9874b1fe 100644 --- a/lam/tests/lib/baseModuleTest.php +++ b/lam/tests/lib/baseModuleTest.php @@ -29,7 +29,7 @@ include_once __DIR__ . '/../../lib/baseModule.inc'; */ class BaseModuleTest extends TestCase { - function setup() { + protected function setup(): void { $_SESSION['language'] = 'en_GB.utf8:UTF-8:English (Great Britain)'; } diff --git a/lam/tests/lib/modules/ppolicyUserTest.php b/lam/tests/lib/modules/ppolicyUserTest.php index 52b92865..f5610e24 100644 --- a/lam/tests/lib/modules/ppolicyUserTest.php +++ b/lam/tests/lib/modules/ppolicyUserTest.php @@ -47,7 +47,7 @@ if (is_readable('lam/lib/modules/ppolicyUser.inc')) { private $options = array(); private $resultLog = null; - public function setUp() { + protected function setUp(): void { $this->job = $this->getMockBuilder('PPolicyPasswordNotifyJob') ->setMethods(array('getDBLastPwdChangeTime', 'setDBLastPwdChangeTime', 'sendMail', 'findUsers', 'getConfigPrefix', 'getPolicyOptions')) diff --git a/lam/tests/lib/modules/shadowAccountTest.php b/lam/tests/lib/modules/shadowAccountTest.php index 2fb8b13c..b576f6bb 100644 --- a/lam/tests/lib/modules/shadowAccountTest.php +++ b/lam/tests/lib/modules/shadowAccountTest.php @@ -133,7 +133,7 @@ if (is_readable('lam/lib/passwordExpirationJob.inc')) { private $options = array(); private $resultLog = null; - public function setUp() { + public function setUp(): void { $this->job = $this->getMockBuilder('ShadowAccountPasswordNotifyJob') ->setMethods(array('getDBLastPwdChangeTime', 'setDBLastPwdChangeTime', 'sendMail', 'findUsers', 'getConfigPrefix')) ->getMock(); diff --git a/lam/tests/lib/securityTest.php b/lam/tests/lib/securityTest.php index 2a5cf4a1..8dfbaa57 100644 --- a/lam/tests/lib/securityTest.php +++ b/lam/tests/lib/securityTest.php @@ -36,7 +36,7 @@ class SecurityTest extends TestCase { private $cfg = null; - protected function setUp() { + protected function setUp(): void { testCreateDefaultConfig (); $this->cfg = &$_SESSION ['cfgMain']; $this->resetPasswordRules(); @@ -45,7 +45,7 @@ class SecurityTest extends TestCase { /** * Cleans up the environment after running a test. */ - protected function tearDown() { + protected function tearDown(): void { testDeleteDefaultConfig(); parent::tearDown(); } diff --git a/lam/tests/lib/typesTest.php b/lam/tests/lib/typesTest.php index b23f9103..6291008f 100644 --- a/lam/tests/lib/typesTest.php +++ b/lam/tests/lib/typesTest.php @@ -33,7 +33,7 @@ class ListAttributeTest extends TestCase { private $type; - public function setUp() { + protected function setUp(): void { $this->type = $this->getMockBuilder('ConfiguredType')->setMethods(array('getBaseType'))->getMock(); $scope = new user($this->type); $this->type->method('getBaseType')->willReturn($scope); From 18eb9ed216fe9c2433051b3ece8bd408f3928a48 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 30 Nov 2019 11:19:53 +0100 Subject: [PATCH 17/51] php 7.3 --- composer.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c75544d4..6bcc8420 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,8 @@ { "require-dev" : { "phpunit/phpunit" : "5.7.27", - "squizlabs/php_codesniffer" : "3.4.0" + "squizlabs/php_codesniffer" : "3.4.0", + "web-auth/webauthn-lib" : "2.1.7", + "symfony/http-foundation" : "5.0.0" } } \ No newline at end of file From 0a30964011facc7692ba907c05835ea56e9b6693 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 30 Nov 2019 11:29:06 +0100 Subject: [PATCH 18/51] php 7.3 --- composer.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 6bcc8420..de5de7f6 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,10 @@ { - "require-dev" : { - "phpunit/phpunit" : "5.7.27", - "squizlabs/php_codesniffer" : "3.4.0", + "require": { "web-auth/webauthn-lib" : "2.1.7", "symfony/http-foundation" : "5.0.0" + }, + "require-dev" : { + "phpunit/phpunit" : "5.7.27", + "squizlabs/php_codesniffer" : "3.4.0" } } \ No newline at end of file From 0f13e3c8ba6c079efa9120da7440d0ecc44341b2 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 30 Nov 2019 14:23:49 +0100 Subject: [PATCH 19/51] webauthn --- lam/composer.json | 3 +- lam/composer.lock | 75 ++++- lam/lib/2factor.inc | 4 +- .../composer/composer/autoload_files.php | 2 +- .../composer/composer/autoload_psr4.php | 1 + .../composer/composer/autoload_static.php | 7 +- .../3rdParty/composer/composer/installed.json | 83 +++++- .../psr-http-message-bridge/.gitignore | 5 + .../psr-http-message-bridge/.php_cs.dist | 24 ++ .../psr-http-message-bridge/.travis.yml | 48 ++++ .../psr-http-message-bridge/CHANGELOG.md | 40 +++ .../Factory/DiactorosFactory.php | 171 +++++++++++ .../Factory/HttpFoundationFactory.php | 240 ++++++++++++++++ .../Factory/PsrHttpFactory.php | 168 +++++++++++ .../Factory/UploadedFile.php | 73 +++++ .../HttpFoundationFactoryInterface.php | 39 +++ .../HttpMessageFactoryInterface.php | 39 +++ .../symfony/psr-http-message-bridge/LICENSE | 19 ++ .../symfony/psr-http-message-bridge/README.md | 20 ++ .../AbstractHttpMessageFactoryTest.php | 217 ++++++++++++++ .../Tests/Factory/DiactorosFactoryTest.php | 33 +++ .../Factory/HttpFoundationFactoryTest.php | 272 ++++++++++++++++++ .../Tests/Factory/PsrHttpFactoryTest.php | 30 ++ .../Tests/Fixtures/Message.php | 93 ++++++ .../Tests/Fixtures/Response.php | 45 +++ .../Tests/Fixtures/ServerRequest.php | 141 +++++++++ .../Tests/Fixtures/Stream.php | 100 +++++++ .../Tests/Fixtures/UploadedFile.php | 65 +++++ .../Tests/Fixtures/Uri.php | 135 +++++++++ .../Tests/Functional/CovertTest.php | 234 +++++++++++++++ .../psr-http-message-bridge/composer.json | 42 +++ .../psr-http-message-bridge/phpunit.xml.dist | 29 ++ lam/lib/webauthn.inc | 12 +- lam/templates/lib/500_lam.js | 1 - lam/templates/misc/ajax.php | 3 +- 35 files changed, 2490 insertions(+), 23 deletions(-) create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/.gitignore create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/.php_cs.dist create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/.travis.yml create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/CHANGELOG.md create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/DiactorosFactory.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/UploadedFile.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/HttpFoundationFactoryInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/HttpMessageFactoryInterface.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/LICENSE create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/README.md create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/AbstractHttpMessageFactoryTest.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/DiactorosFactoryTest.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/HttpFoundationFactoryTest.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/PsrHttpFactoryTest.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Message.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Response.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/ServerRequest.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Stream.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/UploadedFile.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Uri.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Functional/CovertTest.php create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/composer.json create mode 100644 lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/phpunit.xml.dist diff --git a/lam/composer.json b/lam/composer.json index 7eadeac4..68a77fea 100644 --- a/lam/composer.json +++ b/lam/composer.json @@ -4,6 +4,7 @@ }, "require" : { "web-auth/webauthn-lib" : "2.1.7", - "symfony/http-foundation" : "5.0.0" + "symfony/http-foundation" : "5.0.0", + "symfony/psr-http-message-bridge" : "1.3.0" } } \ No newline at end of file diff --git a/lam/composer.lock b/lam/composer.lock index b60ea17c..93f7d6d8 100644 --- a/lam/composer.lock +++ b/lam/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ba2488b4997bb9328901b5be3ed4bed0", + "content-hash": "ef5f7241f5ed768a1c63843aadbb54aa", "packages": [ { "name": "beberlei/assert", @@ -764,7 +764,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.13.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -822,7 +822,7 @@ }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.13.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", @@ -884,7 +884,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.13.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -943,7 +943,7 @@ }, { "name": "symfony/polyfill-php72", - "version": "v1.13.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", @@ -996,6 +996,71 @@ ], "time": "2019-11-27T13:56:44+00:00" }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "9d3e80d54d9ae747ad573cad796e8e247df7b796" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/9d3e80d54d9ae747ad573cad796e8e247df7b796", + "reference": "9d3e80d54d9ae747ad573cad796e8e247df7b796", + "shasum": "" + }, + "require": { + "php": "^7.1", + "psr/http-message": "^1.0", + "symfony/http-foundation": "^4.4 || ^5.0" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "symfony/phpunit-bridge": "^4.4 || ^5.0", + "zendframework/zend-diactoros": "^1.4.1 || ^2.0" + }, + "suggest": { + "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "http://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "time": "2019-11-25T19:33:50+00:00" + }, { "name": "web-auth/cose-lib", "version": "v2.1.7", diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index b50c8c31..677e1ef9 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -10,6 +10,7 @@ use \htmlJavaScript; use \htmlStatusMessage; use \htmlDiv; use \LAMException; +use Webauthn\PublicKeyCredentialCreationOptions; use function LAM\LOGIN\WEBAUTHN\storeNewRegistration; /* @@ -543,9 +544,8 @@ class WebauthnProvider extends BaseProvider { public function verify2ndFactor($user, $password, $serial, $twoFactorInput) { logNewMessage(LOG_DEBUG, 'WebauthnProvider: Checking 2nd factor for ' . $user); $response = base64_decode($_POST['sig_response']); - include_once __DIR__ . '/3rdParty/composer/autoload.php'; include_once __DIR__ . '/webauthn.inc'; - $registrationObject = $_SESSION['webauthn_registration']; + $registrationObject = PublicKeyCredentialCreationOptions::createFromString($_SESSION['webauthn_registration']); if (storeNewRegistration($registrationObject, $response)) { return true; } diff --git a/lam/lib/3rdParty/composer/composer/autoload_files.php b/lam/lib/3rdParty/composer/composer/autoload_files.php index d58d66de..ada90386 100644 --- a/lam/lib/3rdParty/composer/composer/autoload_files.php +++ b/lam/lib/3rdParty/composer/composer/autoload_files.php @@ -9,6 +9,6 @@ return array( '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', 'a4ecaeafb8cfb009ad0e052c90355e98' => $vendorDir . '/beberlei/assert/lib/Assert/functions.php', '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', - '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', ); diff --git a/lam/lib/3rdParty/composer/composer/autoload_psr4.php b/lam/lib/3rdParty/composer/composer/autoload_psr4.php index 5d88d7da..3248ceaa 100644 --- a/lam/lib/3rdParty/composer/composer/autoload_psr4.php +++ b/lam/lib/3rdParty/composer/composer/autoload_psr4.php @@ -14,6 +14,7 @@ return array( 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), 'Symfony\\Component\\Mime\\' => array($vendorDir . '/symfony/mime'), 'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'), + 'Symfony\\Bridge\\PsrHttpMessage\\' => array($vendorDir . '/symfony/psr-http-message-bridge'), 'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'), 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), diff --git a/lam/lib/3rdParty/composer/composer/autoload_static.php b/lam/lib/3rdParty/composer/composer/autoload_static.php index ab4e11ea..30a44942 100644 --- a/lam/lib/3rdParty/composer/composer/autoload_static.php +++ b/lam/lib/3rdParty/composer/composer/autoload_static.php @@ -10,8 +10,8 @@ class ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5 '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', 'a4ecaeafb8cfb009ad0e052c90355e98' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/functions.php', '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', - '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', ); public static $prefixLengthsPsr4 = array ( @@ -28,6 +28,7 @@ class ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5 'Symfony\\Polyfill\\Ctype\\' => 23, 'Symfony\\Component\\Mime\\' => 23, 'Symfony\\Component\\HttpFoundation\\' => 33, + 'Symfony\\Bridge\\PsrHttpMessage\\' => 30, ), 'R' => array ( @@ -98,6 +99,10 @@ class ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5 array ( 0 => __DIR__ . '/..' . '/symfony/http-foundation', ), + 'Symfony\\Bridge\\PsrHttpMessage\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/psr-http-message-bridge', + ), 'Ramsey\\Uuid\\' => array ( 0 => __DIR__ . '/..' . '/ramsey/uuid/src', diff --git a/lam/lib/3rdParty/composer/composer/installed.json b/lam/lib/3rdParty/composer/composer/installed.json index aada8d20..6363f5fb 100644 --- a/lam/lib/3rdParty/composer/composer/installed.json +++ b/lam/lib/3rdParty/composer/composer/installed.json @@ -783,8 +783,8 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.13.0", - "version_normalized": "1.13.0.0", + "version": "v1.13.1", + "version_normalized": "1.13.1.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -843,8 +843,8 @@ }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.13.0", - "version_normalized": "1.13.0.0", + "version": "v1.13.1", + "version_normalized": "1.13.1.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", @@ -907,8 +907,8 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.13.0", - "version_normalized": "1.13.0.0", + "version": "v1.13.1", + "version_normalized": "1.13.1.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -968,8 +968,8 @@ }, { "name": "symfony/polyfill-php72", - "version": "v1.13.0", - "version_normalized": "1.13.0.0", + "version": "v1.13.1", + "version_normalized": "1.13.1.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", @@ -1023,6 +1023,73 @@ "shim" ] }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "9d3e80d54d9ae747ad573cad796e8e247df7b796" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/9d3e80d54d9ae747ad573cad796e8e247df7b796", + "reference": "9d3e80d54d9ae747ad573cad796e8e247df7b796", + "shasum": "" + }, + "require": { + "php": "^7.1", + "psr/http-message": "^1.0", + "symfony/http-foundation": "^4.4 || ^5.0" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "symfony/phpunit-bridge": "^4.4 || ^5.0", + "zendframework/zend-diactoros": "^1.4.1 || ^2.0" + }, + "suggest": { + "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + }, + "time": "2019-11-25T19:33:50+00:00", + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "http://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ] + }, { "name": "web-auth/cose-lib", "version": "v2.1.7", diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/.gitignore b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/.gitignore new file mode 100644 index 00000000..082fd221 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/.gitignore @@ -0,0 +1,5 @@ +vendor/ +composer.lock +phpunit.xml +.php_cs.cache +.phpunit.result.cache diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/.php_cs.dist b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/.php_cs.dist new file mode 100644 index 00000000..d741d39f --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/.php_cs.dist @@ -0,0 +1,24 @@ +setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + '@PHPUnit48Migration:risky' => true, + 'php_unit_no_expectation_annotation' => false, // part of `PHPUnitXYMigration:risky` ruleset, to be enabled when PHPUnit 4.x support will be dropped, as we don't want to rewrite exceptions handling twice + 'array_syntax' => ['syntax' => 'short'], + 'fopen_flags' => false, + 'ordered_imports' => true, + 'protected_to_private' => false, + // Part of @Symfony:risky in PHP-CS-Fixer 2.13.0. To be removed from the config file once upgrading + 'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'namespaced'], + // Part of future @Symfony ruleset in PHP-CS-Fixer To be removed from the config file once upgrading + 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + ]) + ->setRiskyAllowed(true) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__) + ->name('*.php') + ) +; diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/.travis.yml b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/.travis.yml new file mode 100644 index 00000000..d171c003 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/.travis.yml @@ -0,0 +1,48 @@ +language: php +sudo: false +cache: + directories: + - $HOME/.composer/cache/files + - $HOME/symfony-bridge/.phpunit + +env: + global: + - PHPUNIT_FLAGS="-v" + - SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit" + +matrix: + fast_finish: true + include: + # Minimum supported dependencies with the latest and oldest PHP version + - php: 7.3 + env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors" + + - php: 7.1 + - php: 7.2 + - php: 7.3 + env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text" + + # Latest commit to master + - php: 7.3 + env: STABILITY="dev" + + allow_failures: + # Dev-master is allowed to fail. + - env: STABILITY="dev" + +before_install: + - if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi + - if ! [ -z "$STABILITY" ]; then composer config minimum-stability ${STABILITY}; fi; + - if ! [ -v "$DEPENDENCIES" ]; then composer require --no-update ${DEPENDENCIES}; fi; + +install: + # To be removed when this issue will be resolved: https://github.com/composer/composer/issues/5355 + - if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi + - composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction + - ./vendor/bin/simple-phpunit install + +script: + - composer validate --strict --no-check-lock + # simple-phpunit is the PHPUnit wrapper provided by the PHPUnit Bridge component and + # it helps with testing legacy code and deprecations (composer require symfony/phpunit-bridge) + - ./vendor/bin/simple-phpunit $PHPUNIT_FLAGS diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/CHANGELOG.md b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/CHANGELOG.md new file mode 100644 index 00000000..1559be28 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/CHANGELOG.md @@ -0,0 +1,40 @@ +CHANGELOG +========= + +* 1.3.0 (2019-11-25) + + * Added support for streamed requests + * Added support for Symfony 5.0+ + * Fixed bridging UploadedFile objects + * Bumped minimum version of Symfony to 4.4 + +* 1.2.0 (2019-03-11) + + * Added new documentation links + * Bumped minimum version of PHP to 7.1 + * Added support for streamed responses + +* 1.1.2 (2019-04-03) + + * Fixed createResponse + +* 1.1.1 (2019-03-11) + + * Deprecated DiactorosFactory, use PsrHttpFactory instead + * Removed triggering of deprecation + +* 1.1.0 (2018-08-30) + + * Added support for creating PSR-7 messages using PSR-17 factories + +* 1.0.2 (2017-12-19) + + * Fixed request target in PSR7 Request (mtibben) + +* 1.0.1 (2017-12-04) + + * Added support for Symfony 4 (dunglas) + +* 1.0.0 (2016-09-14) + + * Initial release diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/DiactorosFactory.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/DiactorosFactory.php new file mode 100644 index 00000000..cc87dd82 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/DiactorosFactory.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Factory; + +@trigger_error(sprintf('The "%s" class is deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instead.', DiactorosFactory::class), E_USER_DEPRECATED); + +use Psr\Http\Message\UploadedFileInterface; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Zend\Diactoros\Response as DiactorosResponse; +use Zend\Diactoros\ServerRequest; +use Zend\Diactoros\ServerRequestFactory as DiactorosRequestFactory; +use Zend\Diactoros\Stream as DiactorosStream; +use Zend\Diactoros\UploadedFile as DiactorosUploadedFile; + +/** + * Builds Psr\HttpMessage instances using the Zend Diactoros implementation. + * + * @author Kévin Dunglas + * + * @deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instead + */ +class DiactorosFactory implements HttpMessageFactoryInterface +{ + public function __construct() + { + if (!class_exists('Zend\Diactoros\ServerRequestFactory')) { + throw new \RuntimeException('Zend Diactoros must be installed to use the DiactorosFactory.'); + } + } + + /** + * {@inheritdoc} + */ + public function createRequest(Request $symfonyRequest) + { + $server = method_exists('Zend\Diactoros\ServerRequestFactory', 'normalizeServer') + ? DiactorosRequestFactory::normalizeServer($symfonyRequest->server->all()) + : \Zend\Diactoros\normalizeServer($symfonyRequest->server->all()); + $headers = $symfonyRequest->headers->all(); + + $body = new DiactorosStream($symfonyRequest->getContent(true)); + + $files = method_exists('Zend\Diactoros\ServerRequestFactory', 'normalizeFiles') + ? DiactorosRequestFactory::normalizeFiles($this->getFiles($symfonyRequest->files->all())) + : \Zend\Diactoros\normalizeUploadedFiles($this->getFiles($symfonyRequest->files->all())); + + $request = new ServerRequest( + $server, + $files, + $symfonyRequest->getSchemeAndHttpHost().$symfonyRequest->getRequestUri(), + $symfonyRequest->getMethod(), + $body, + $headers + ); + + $request = $request + ->withCookieParams($symfonyRequest->cookies->all()) + ->withQueryParams($symfonyRequest->query->all()) + ->withParsedBody($symfonyRequest->request->all()) + ->withRequestTarget($symfonyRequest->getRequestUri()) + ; + + foreach ($symfonyRequest->attributes->all() as $key => $value) { + $request = $request->withAttribute($key, $value); + } + + return $request; + } + + /** + * Converts Symfony uploaded files array to the PSR one. + * + * @return array + */ + private function getFiles(array $uploadedFiles) + { + $files = []; + + foreach ($uploadedFiles as $key => $value) { + if (null === $value) { + $files[$key] = new DiactorosUploadedFile(null, 0, UPLOAD_ERR_NO_FILE, null, null); + continue; + } + if ($value instanceof UploadedFile) { + $files[$key] = $this->createUploadedFile($value); + } else { + $files[$key] = $this->getFiles($value); + } + } + + return $files; + } + + /** + * Creates a PSR-7 UploadedFile instance from a Symfony one. + * + * @return UploadedFileInterface + */ + private function createUploadedFile(UploadedFile $symfonyUploadedFile) + { + return new DiactorosUploadedFile( + $symfonyUploadedFile->getRealPath(), + (int) $symfonyUploadedFile->getSize(), + $symfonyUploadedFile->getError(), + $symfonyUploadedFile->getClientOriginalName(), + $symfonyUploadedFile->getClientMimeType() + ); + } + + /** + * {@inheritdoc} + */ + public function createResponse(Response $symfonyResponse) + { + if ($symfonyResponse instanceof BinaryFileResponse) { + $stream = new DiactorosStream($symfonyResponse->getFile()->getPathname(), 'r'); + } else { + $stream = new DiactorosStream('php://temp', 'wb+'); + if ($symfonyResponse instanceof StreamedResponse) { + ob_start(function ($buffer) use ($stream) { + $stream->write($buffer); + + return ''; + }); + + $symfonyResponse->sendContent(); + ob_end_clean(); + } else { + $stream->write($symfonyResponse->getContent()); + } + } + + $headers = $symfonyResponse->headers->all(); + if (!isset($headers['Set-Cookie']) && !isset($headers['set-cookie'])) { + $cookies = $symfonyResponse->headers->getCookies(); + if (!empty($cookies)) { + $headers['Set-Cookie'] = []; + foreach ($cookies as $cookie) { + $headers['Set-Cookie'][] = $cookie->__toString(); + } + } + } + + $response = new DiactorosResponse( + $stream, + $symfonyResponse->getStatusCode(), + $headers + ); + + $protocolVersion = $symfonyResponse->getProtocolVersion(); + if ('1.1' !== $protocolVersion) { + $response = $response->withProtocolVersion($protocolVersion); + } + + return $response; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php new file mode 100644 index 00000000..d9bbaf2d --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php @@ -0,0 +1,240 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Factory; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UploadedFileInterface; +use Psr\Http\Message\UriInterface; +use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * {@inheritdoc} + * + * @author Kévin Dunglas + */ +class HttpFoundationFactory implements HttpFoundationFactoryInterface +{ + /** + * @var int The maximum output buffering size for each iteration when sending the response + */ + private $responseBufferMaxLength; + + public function __construct(int $responseBufferMaxLength = 16372) + { + $this->responseBufferMaxLength = $responseBufferMaxLength; + } + + /** + * {@inheritdoc} + */ + public function createRequest(ServerRequestInterface $psrRequest, bool $streamed = false) + { + $server = []; + $uri = $psrRequest->getUri(); + + if ($uri instanceof UriInterface) { + $server['SERVER_NAME'] = $uri->getHost(); + $server['SERVER_PORT'] = $uri->getPort(); + $server['REQUEST_URI'] = $uri->getPath(); + $server['QUERY_STRING'] = $uri->getQuery(); + } + + $server['REQUEST_METHOD'] = $psrRequest->getMethod(); + + $server = array_replace($server, $psrRequest->getServerParams()); + + $parsedBody = $psrRequest->getParsedBody(); + $parsedBody = \is_array($parsedBody) ? $parsedBody : []; + + $request = new Request( + $psrRequest->getQueryParams(), + $parsedBody, + $psrRequest->getAttributes(), + $psrRequest->getCookieParams(), + $this->getFiles($psrRequest->getUploadedFiles()), + $server, + $streamed ? $psrRequest->getBody()->detach() : $psrRequest->getBody()->__toString() + ); + $request->headers->replace($psrRequest->getHeaders()); + + return $request; + } + + /** + * Converts to the input array to $_FILES structure. + */ + private function getFiles(array $uploadedFiles): array + { + $files = []; + + foreach ($uploadedFiles as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $files[$key] = $this->createUploadedFile($value); + } else { + $files[$key] = $this->getFiles($value); + } + } + + return $files; + } + + /** + * Creates Symfony UploadedFile instance from PSR-7 ones. + */ + private function createUploadedFile(UploadedFileInterface $psrUploadedFile): UploadedFile + { + return new UploadedFile($psrUploadedFile, function () { return $this->getTemporaryPath(); }); + } + + /** + * Gets a temporary file path. + * + * @return string + */ + protected function getTemporaryPath() + { + return tempnam(sys_get_temp_dir(), uniqid('symfony', true)); + } + + /** + * {@inheritdoc} + */ + public function createResponse(ResponseInterface $psrResponse, bool $streamed = false) + { + $cookies = $psrResponse->getHeader('Set-Cookie'); + $psrResponse = $psrResponse->withoutHeader('Set-Cookie'); + + if ($streamed) { + $response = new StreamedResponse( + $this->createStreamedResponseCallback($psrResponse->getBody()), + $psrResponse->getStatusCode(), + $psrResponse->getHeaders() + ); + } else { + $response = new Response( + $psrResponse->getBody()->__toString(), + $psrResponse->getStatusCode(), + $psrResponse->getHeaders() + ); + } + + $response->setProtocolVersion($psrResponse->getProtocolVersion()); + + foreach ($cookies as $cookie) { + $response->headers->setCookie($this->createCookie($cookie)); + } + + return $response; + } + + /** + * Creates a Cookie instance from a cookie string. + * + * Some snippets have been taken from the Guzzle project: https://github.com/guzzle/guzzle/blob/5.3/src/Cookie/SetCookie.php#L34 + * + * @throws \InvalidArgumentException + */ + private function createCookie(string $cookie): Cookie + { + foreach (explode(';', $cookie) as $part) { + $part = trim($part); + + $data = explode('=', $part, 2); + $name = $data[0]; + $value = isset($data[1]) ? trim($data[1], " \n\r\t\0\x0B\"") : null; + + if (!isset($cookieName)) { + $cookieName = $name; + $cookieValue = $value; + + continue; + } + + if ('expires' === strtolower($name) && null !== $value) { + $cookieExpire = new \DateTime($value); + + continue; + } + + if ('path' === strtolower($name) && null !== $value) { + $cookiePath = $value; + + continue; + } + + if ('domain' === strtolower($name) && null !== $value) { + $cookieDomain = $value; + + continue; + } + + if ('secure' === strtolower($name)) { + $cookieSecure = true; + + continue; + } + + if ('httponly' === strtolower($name)) { + $cookieHttpOnly = true; + + continue; + } + + if ('samesite' === strtolower($name) && null !== $value) { + $samesite = $value; + + continue; + } + } + + if (!isset($cookieName)) { + throw new \InvalidArgumentException('The value of the Set-Cookie header is malformed.'); + } + + return new Cookie( + $cookieName, + $cookieValue, + isset($cookieExpire) ? $cookieExpire : 0, + isset($cookiePath) ? $cookiePath : '/', + isset($cookieDomain) ? $cookieDomain : null, + isset($cookieSecure), + isset($cookieHttpOnly), + false, + isset($samesite) ? $samesite : null + ); + } + + private function createStreamedResponseCallback(StreamInterface $body): callable + { + return function () use ($body) { + if ($body->isSeekable()) { + $body->rewind(); + } + + if (!$body->isReadable()) { + echo $body; + + return; + } + + while (!$body->eof()) { + echo $body->read($this->responseBufferMaxLength); + } + }; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php new file mode 100644 index 00000000..dfcfe73b --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php @@ -0,0 +1,168 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Factory; + +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ServerRequestFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\UploadedFileFactoryInterface; +use Psr\Http\Message\UploadedFileInterface; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * Builds Psr\HttpMessage instances using a PSR-17 implementation. + * + * @author Antonio J. García Lagar + */ +class PsrHttpFactory implements HttpMessageFactoryInterface +{ + private $serverRequestFactory; + private $streamFactory; + private $uploadedFileFactory; + private $responseFactory; + + public function __construct(ServerRequestFactoryInterface $serverRequestFactory, StreamFactoryInterface $streamFactory, UploadedFileFactoryInterface $uploadedFileFactory, ResponseFactoryInterface $responseFactory) + { + $this->serverRequestFactory = $serverRequestFactory; + $this->streamFactory = $streamFactory; + $this->uploadedFileFactory = $uploadedFileFactory; + $this->responseFactory = $responseFactory; + } + + /** + * {@inheritdoc} + */ + public function createRequest(Request $symfonyRequest) + { + $request = $this->serverRequestFactory->createServerRequest( + $symfonyRequest->getMethod(), + $symfonyRequest->getUri(), + $symfonyRequest->server->all() + ); + + foreach ($symfonyRequest->headers->all() as $name => $value) { + $request = $request->withHeader($name, $value); + } + + $body = $this->streamFactory->createStreamFromResource($symfonyRequest->getContent(true)); + + $request = $request + ->withBody($body) + ->withUploadedFiles($this->getFiles($symfonyRequest->files->all())) + ->withCookieParams($symfonyRequest->cookies->all()) + ->withQueryParams($symfonyRequest->query->all()) + ->withParsedBody($symfonyRequest->request->all()) + ; + + foreach ($symfonyRequest->attributes->all() as $key => $value) { + $request = $request->withAttribute($key, $value); + } + + return $request; + } + + /** + * Converts Symfony uploaded files array to the PSR one. + * + * @return array + */ + private function getFiles(array $uploadedFiles) + { + $files = []; + + foreach ($uploadedFiles as $key => $value) { + if (null === $value) { + $files[$key] = $this->uploadedFileFactory->createUploadedFile($this->streamFactory->createStream(), 0, UPLOAD_ERR_NO_FILE); + continue; + } + if ($value instanceof UploadedFile) { + $files[$key] = $this->createUploadedFile($value); + } else { + $files[$key] = $this->getFiles($value); + } + } + + return $files; + } + + /** + * Creates a PSR-7 UploadedFile instance from a Symfony one. + * + * @return UploadedFileInterface + */ + private function createUploadedFile(UploadedFile $symfonyUploadedFile) + { + return $this->uploadedFileFactory->createUploadedFile( + $this->streamFactory->createStreamFromFile( + $symfonyUploadedFile->getRealPath() + ), + (int) $symfonyUploadedFile->getSize(), + $symfonyUploadedFile->getError(), + $symfonyUploadedFile->getClientOriginalName(), + $symfonyUploadedFile->getClientMimeType() + ); + } + + /** + * {@inheritdoc} + */ + public function createResponse(Response $symfonyResponse) + { + $response = $this->responseFactory->createResponse($symfonyResponse->getStatusCode(), Response::$statusTexts[$symfonyResponse->getStatusCode()] ?? ''); + + if ($symfonyResponse instanceof BinaryFileResponse) { + $stream = $this->streamFactory->createStreamFromFile( + $symfonyResponse->getFile()->getPathname() + ); + } else { + $stream = $this->streamFactory->createStreamFromFile('php://temp', 'wb+'); + if ($symfonyResponse instanceof StreamedResponse) { + ob_start(function ($buffer) use ($stream) { + $stream->write($buffer); + + return ''; + }); + + $symfonyResponse->sendContent(); + ob_end_clean(); + } else { + $stream->write($symfonyResponse->getContent()); + } + } + + $response = $response->withBody($stream); + + $headers = $symfonyResponse->headers->all(); + $cookies = $symfonyResponse->headers->getCookies(); + if (!empty($cookies)) { + $headers['Set-Cookie'] = []; + + foreach ($cookies as $cookie) { + $headers['Set-Cookie'][] = $cookie->__toString(); + } + } + + foreach ($headers as $name => $value) { + $response = $response->withHeader($name, $value); + } + + $protocolVersion = $symfonyResponse->getProtocolVersion(); + $response = $response->withProtocolVersion($protocolVersion); + + return $response; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/UploadedFile.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/UploadedFile.php new file mode 100644 index 00000000..804f7478 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Factory/UploadedFile.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Factory; + +use Psr\Http\Message\UploadedFileInterface; +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\UploadedFile as BaseUploadedFile; + +/** + * @author Nicolas Grekas + */ +class UploadedFile extends BaseUploadedFile +{ + private $psrUploadedFile; + private $test = false; + + public function __construct(UploadedFileInterface $psrUploadedFile, callable $getTemporaryPath) + { + $error = $psrUploadedFile->getError(); + $path = ''; + + if (UPLOAD_ERR_NO_FILE !== $error) { + $path = $psrUploadedFile->getStream()->getMetadata('uri') ?? ''; + + if ($this->test = !\is_string($path) || !is_uploaded_file($path)) { + $path = $getTemporaryPath(); + $psrUploadedFile->moveTo($path); + } + } + + parent::__construct( + $path, + (string) $psrUploadedFile->getClientFilename(), + $psrUploadedFile->getClientMediaType(), + $psrUploadedFile->getError(), + $this->test + ); + + $this->psrUploadedFile = $psrUploadedFile; + } + + /** + * {@inheritdoc} + */ + public function move($directory, $name = null): File + { + if (!$this->isValid() || $this->test) { + return parent::move($directory, $name); + } + + $target = $this->getTargetFile($directory, $name); + + try { + $this->psrUploadedFile->moveTo($target); + } catch (\RuntimeException $e) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, $e->getMessage()), 0, $e); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/HttpFoundationFactoryInterface.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/HttpFoundationFactoryInterface.php new file mode 100644 index 00000000..a3f90438 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/HttpFoundationFactoryInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Creates Symfony Request and Response instances from PSR-7 ones. + * + * @author Kévin Dunglas + */ +interface HttpFoundationFactoryInterface +{ + /** + * Creates a Symfony Request instance from a PSR-7 one. + * + * @return Request + */ + public function createRequest(ServerRequestInterface $psrRequest, bool $streamed = false); + + /** + * Creates a Symfony Response instance from a PSR-7 one. + * + * @return Response + */ + public function createResponse(ResponseInterface $psrResponse, bool $streamed = false); +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/HttpMessageFactoryInterface.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/HttpMessageFactoryInterface.php new file mode 100644 index 00000000..f7b964e1 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/HttpMessageFactoryInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Creates PSR HTTP Request and Response instances from Symfony ones. + * + * @author Kévin Dunglas + */ +interface HttpMessageFactoryInterface +{ + /** + * Creates a PSR-7 Request instance from a Symfony one. + * + * @return ServerRequestInterface + */ + public function createRequest(Request $symfonyRequest); + + /** + * Creates a PSR-7 Response instance from a Symfony one. + * + * @return ResponseInterface + */ + public function createResponse(Response $symfonyResponse); +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/LICENSE b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/LICENSE new file mode 100644 index 00000000..12a74531 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2016 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/README.md b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/README.md new file mode 100644 index 00000000..87fbd43a --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/README.md @@ -0,0 +1,20 @@ +PSR-7 Bridge +============ + +Provides integration for PSR7. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/psr7.html) + * [SensioFrameworkExtraBundle](https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html#psr-7-support) + +Running the tests +----------------- + +If you want to run the unit tests, install dev dependencies before +running PHPUnit: + + $ cd path/to/Symfony/Bridge/PsrHttpMessage/ + $ composer.phar install + $ phpunit diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/AbstractHttpMessageFactoryTest.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/AbstractHttpMessageFactoryTest.php new file mode 100644 index 00000000..998edcc2 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/AbstractHttpMessageFactoryTest.php @@ -0,0 +1,217 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * @author Kévin Dunglas + * @author Antonio J. García Lagar + */ +abstract class AbstractHttpMessageFactoryTest extends TestCase +{ + private $factory; + private $tmpDir; + + abstract protected function buildHttpMessageFactory(): HttpMessageFactoryInterface; + + public function setUp(): void + { + $this->factory = $this->buildHttpMessageFactory(); + $this->tmpDir = sys_get_temp_dir(); + } + + public function testCreateRequest() + { + $stdClass = new \stdClass(); + $request = new Request( + [ + 'bar' => ['baz' => '42'], + 'foo' => '1', + ], + [ + 'twitter' => [ + '@dunglas' => 'Kévin Dunglas', + '@coopTilleuls' => 'Les-Tilleuls.coop', + ], + 'baz' => '2', + ], + [ + 'a1' => $stdClass, + 'a2' => ['foo' => 'bar'], + ], + [ + 'c1' => 'foo', + 'c2' => ['c3' => 'bar'], + ], + [ + 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', UPLOAD_ERR_OK), + 'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', UPLOAD_ERR_OK)], + ], + [ + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'dunglas.fr', + 'HTTP_X_SYMFONY' => '2.8', + 'REQUEST_URI' => '/testCreateRequest?bar[baz]=42&foo=1', + 'QUERY_STRING' => 'bar[baz]=42&foo=1', + ], + 'Content' + ); + + $psrRequest = $this->factory->createRequest($request); + + $this->assertEquals('Content', $psrRequest->getBody()->__toString()); + + $queryParams = $psrRequest->getQueryParams(); + $this->assertEquals('1', $queryParams['foo']); + $this->assertEquals('42', $queryParams['bar']['baz']); + + $requestTarget = $psrRequest->getRequestTarget(); + $this->assertEquals('/testCreateRequest?bar[baz]=42&foo=1', urldecode($requestTarget)); + + $parsedBody = $psrRequest->getParsedBody(); + $this->assertEquals('Kévin Dunglas', $parsedBody['twitter']['@dunglas']); + $this->assertEquals('Les-Tilleuls.coop', $parsedBody['twitter']['@coopTilleuls']); + $this->assertEquals('2', $parsedBody['baz']); + + $attributes = $psrRequest->getAttributes(); + $this->assertEquals($stdClass, $attributes['a1']); + $this->assertEquals('bar', $attributes['a2']['foo']); + + $cookies = $psrRequest->getCookieParams(); + $this->assertEquals('foo', $cookies['c1']); + $this->assertEquals('bar', $cookies['c2']['c3']); + + $uploadedFiles = $psrRequest->getUploadedFiles(); + $this->assertEquals('F1', $uploadedFiles['f1']->getStream()->__toString()); + $this->assertEquals('f1.txt', $uploadedFiles['f1']->getClientFilename()); + $this->assertEquals('text/plain', $uploadedFiles['f1']->getClientMediaType()); + $this->assertEquals(UPLOAD_ERR_OK, $uploadedFiles['f1']->getError()); + + $this->assertEquals('F2', $uploadedFiles['foo']['f2']->getStream()->__toString()); + $this->assertEquals('f2.txt', $uploadedFiles['foo']['f2']->getClientFilename()); + $this->assertEquals('text/plain', $uploadedFiles['foo']['f2']->getClientMediaType()); + $this->assertEquals(UPLOAD_ERR_OK, $uploadedFiles['foo']['f2']->getError()); + + $serverParams = $psrRequest->getServerParams(); + $this->assertEquals('POST', $serverParams['REQUEST_METHOD']); + $this->assertEquals('2.8', $serverParams['HTTP_X_SYMFONY']); + $this->assertEquals('POST', $psrRequest->getMethod()); + $this->assertEquals(['2.8'], $psrRequest->getHeader('X-Symfony')); + } + + public function testGetContentCanBeCalledAfterRequestCreation() + { + $header = ['HTTP_HOST' => 'dunglas.fr']; + $request = new Request([], [], [], [], [], $header, 'Content'); + + $psrRequest = $this->factory->createRequest($request); + + $this->assertEquals('Content', $psrRequest->getBody()->__toString()); + $this->assertEquals('Content', $request->getContent()); + } + + private function createUploadedFile($content, $originalName, $mimeType, $error) + { + $path = tempnam($this->tmpDir, uniqid()); + file_put_contents($path, $content); + + return new UploadedFile($path, $originalName, $mimeType, $error, true); + } + + public function testCreateResponse() + { + $response = new Response( + 'Response content.', + 202, + ['X-Symfony' => ['3.4']] + ); + $response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'), '/', null, false, true, false, 'lax')); + + $psrResponse = $this->factory->createResponse($response); + $this->assertEquals('Response content.', $psrResponse->getBody()->__toString()); + $this->assertEquals(202, $psrResponse->getStatusCode()); + $this->assertEquals(['3.4'], $psrResponse->getHeader('X-Symfony')); + + $cookieHeader = $psrResponse->getHeader('Set-Cookie'); + $this->assertIsArray($cookieHeader); + $this->assertCount(1, $cookieHeader); + $this->assertRegExp('{city=Lille; expires=Wed, 13-Jan-2021 22:23:01 GMT;( max-age=\d+;)? path=/; httponly}i', $cookieHeader[0]); + } + + public function testCreateResponseFromStreamed() + { + $response = new StreamedResponse(function () { + echo "Line 1\n"; + flush(); + + echo "Line 2\n"; + flush(); + }); + + $psrResponse = $this->factory->createResponse($response); + + $this->assertEquals("Line 1\nLine 2\n", $psrResponse->getBody()->__toString()); + } + + public function testCreateResponseFromBinaryFile() + { + $path = tempnam($this->tmpDir, uniqid()); + file_put_contents($path, 'Binary'); + + $response = new BinaryFileResponse($path); + + $psrResponse = $this->factory->createResponse($response); + + $this->assertEquals('Binary', $psrResponse->getBody()->__toString()); + } + + public function testUploadErrNoFile() + { + $file = new UploadedFile('', '', null, UPLOAD_ERR_NO_FILE, true); + + $this->assertEquals(0, $file->getSize()); + $this->assertEquals(UPLOAD_ERR_NO_FILE, $file->getError()); + $this->assertFalse($file->getSize(), 'SplFile::getSize() returns false on error'); + + $request = new Request( + [], + [], + [], + [], + [ + 'f1' => $file, + 'f2' => ['name' => null, 'type' => null, 'tmp_name' => null, 'error' => UPLOAD_ERR_NO_FILE, 'size' => 0], + ], + [ + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'dunglas.fr', + 'HTTP_X_SYMFONY' => '2.8', + ], + 'Content' + ); + + $psrRequest = $this->factory->createRequest($request); + + $uploadedFiles = $psrRequest->getUploadedFiles(); + + $this->assertEquals(UPLOAD_ERR_NO_FILE, $uploadedFiles['f1']->getError()); + $this->assertEquals(UPLOAD_ERR_NO_FILE, $uploadedFiles['f2']->getError()); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/DiactorosFactoryTest.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/DiactorosFactoryTest.php new file mode 100644 index 00000000..9d0fc059 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/DiactorosFactoryTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; + +use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; + +/** + * @author Kévin Dunglas + * @author Antonio J. García Lagar + * + * @group legacy + */ +class DiactorosFactoryTest extends AbstractHttpMessageFactoryTest +{ + protected function buildHttpMessageFactory(): HttpMessageFactoryInterface + { + if (!class_exists('Zend\Diactoros\ServerRequestFactory')) { + $this->markTestSkipped('Zend Diactoros is not installed.'); + } + + return new DiactorosFactory(); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/HttpFoundationFactoryTest.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/HttpFoundationFactoryTest.php new file mode 100644 index 00000000..e35a7893 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/HttpFoundationFactoryTest.php @@ -0,0 +1,272 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; + +use PHPUnit\Framework\TestCase; +use Psr\Http\Message\UploadedFileInterface; +use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Response; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\ServerRequest; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Stream; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\UploadedFile; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Uri; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\UploadedFile as HttpFoundationUploadedFile; + +/** + * @author Kévin Dunglas + */ +class HttpFoundationFactoryTest extends TestCase +{ + /** @var HttpFoundationFactory */ + private $factory; + + /** @var string */ + private $tmpDir; + + public function setUp(): void + { + $this->factory = new HttpFoundationFactory(); + $this->tmpDir = sys_get_temp_dir(); + } + + public function testCreateRequest() + { + $stdClass = new \stdClass(); + $serverRequest = new ServerRequest( + '1.1', + [ + 'X-Dunglas-API-Platform' => '1.0', + 'X-data' => ['a', 'b'], + ], + new Stream('The body'), + '/about/kevin', + 'GET', + 'http://les-tilleuls.coop/about/kevin', + ['country' => 'France'], + ['city' => 'Lille'], + ['url' => 'http://les-tilleuls.coop'], + [ + 'doc1' => $this->createUploadedFile('Doc 1', UPLOAD_ERR_OK, 'doc1.txt', 'text/plain'), + 'nested' => [ + 'docs' => [ + $this->createUploadedFile('Doc 2', UPLOAD_ERR_OK, 'doc2.txt', 'text/plain'), + $this->createUploadedFile('Doc 3', UPLOAD_ERR_OK, 'doc3.txt', 'text/plain'), + ], + ], + ], + ['url' => 'http://dunglas.fr'], + ['custom' => $stdClass] + ); + + $symfonyRequest = $this->factory->createRequest($serverRequest); + $files = $symfonyRequest->files->all(); + + $this->assertEquals('http://les-tilleuls.coop', $symfonyRequest->query->get('url')); + $this->assertEquals('doc1.txt', $files['doc1']->getClientOriginalName()); + $this->assertEquals('doc2.txt', $files['nested']['docs'][0]->getClientOriginalName()); + $this->assertEquals('doc3.txt', $files['nested']['docs'][1]->getClientOriginalName()); + $this->assertEquals('http://dunglas.fr', $symfonyRequest->request->get('url')); + $this->assertEquals($stdClass, $symfonyRequest->attributes->get('custom')); + $this->assertEquals('Lille', $symfonyRequest->cookies->get('city')); + $this->assertEquals('France', $symfonyRequest->server->get('country')); + $this->assertEquals('The body', $symfonyRequest->getContent()); + $this->assertEquals('1.0', $symfonyRequest->headers->get('X-Dunglas-API-Platform')); + $this->assertEquals(['a', 'b'], $symfonyRequest->headers->all('X-data')); + } + + public function testCreateRequestWithStreamedBody() + { + $serverRequest = new ServerRequest( + '1.1', + [], + new Stream('The body'), + '/', + 'GET', + null, + [], + [], + [], + [], + null, + [] + ); + + $symfonyRequest = $this->factory->createRequest($serverRequest, true); + $this->assertEquals('The body', $symfonyRequest->getContent()); + } + + public function testCreateRequestWithNullParsedBody() + { + $serverRequest = new ServerRequest( + '1.1', + [], + new Stream(), + '/', + 'GET', + null, + [], + [], + [], + [], + null, + [] + ); + + $this->assertCount(0, $this->factory->createRequest($serverRequest)->request); + } + + public function testCreateRequestWithObjectParsedBody() + { + $serverRequest = new ServerRequest( + '1.1', + [], + new Stream(), + '/', + 'GET', + null, + [], + [], + [], + [], + new \stdClass(), + [] + ); + + $this->assertCount(0, $this->factory->createRequest($serverRequest)->request); + } + + public function testCreateRequestWithUri() + { + $serverRequest = new ServerRequest( + '1.1', + [], + new Stream(), + '/', + 'GET', + new Uri('http://les-tilleuls.coop/about/kevin'), + [], + [], + [], + [], + null, + [] + ); + + $this->assertEquals('/about/kevin', $this->factory->createRequest($serverRequest)->getPathInfo()); + } + + public function testCreateUploadedFile() + { + $uploadedFile = $this->createUploadedFile('An uploaded file.', UPLOAD_ERR_OK, 'myfile.txt', 'text/plain'); + $symfonyUploadedFile = $this->callCreateUploadedFile($uploadedFile); + $size = $symfonyUploadedFile->getSize(); + + $uniqid = uniqid(); + $symfonyUploadedFile->move($this->tmpDir, $uniqid); + + $this->assertEquals($uploadedFile->getSize(), $size); + $this->assertEquals(UPLOAD_ERR_OK, $symfonyUploadedFile->getError()); + $this->assertEquals('myfile.txt', $symfonyUploadedFile->getClientOriginalName()); + $this->assertEquals('txt', $symfonyUploadedFile->getClientOriginalExtension()); + $this->assertEquals('text/plain', $symfonyUploadedFile->getClientMimeType()); + $this->assertEquals('An uploaded file.', file_get_contents($this->tmpDir.'/'.$uniqid)); + } + + public function testCreateUploadedFileWithError() + { + $this->expectException(FileException::class); + $this->expectExceptionMessage('The file "e" could not be written on disk.'); + + $uploadedFile = $this->createUploadedFile('Error.', UPLOAD_ERR_CANT_WRITE, 'e', 'text/plain'); + $symfonyUploadedFile = $this->callCreateUploadedFile($uploadedFile); + + $this->assertEquals(UPLOAD_ERR_CANT_WRITE, $symfonyUploadedFile->getError()); + + $symfonyUploadedFile->move($this->tmpDir, 'shouldFail.txt'); + } + + private function createUploadedFile($content, $error, $clientFileName, $clientMediaType): UploadedFile + { + $filePath = tempnam($this->tmpDir, uniqid()); + file_put_contents($filePath, $content); + + return new UploadedFile($filePath, filesize($filePath), $error, $clientFileName, $clientMediaType); + } + + private function callCreateUploadedFile(UploadedFileInterface $uploadedFile): HttpFoundationUploadedFile + { + $reflection = new \ReflectionClass($this->factory); + $createUploadedFile = $reflection->getMethod('createUploadedFile'); + $createUploadedFile->setAccessible(true); + + return $createUploadedFile->invokeArgs($this->factory, [$uploadedFile]); + } + + public function testCreateResponse() + { + $response = new Response( + '1.0', + [ + 'X-Symfony' => ['2.8'], + 'Set-Cookie' => [ + 'theme=light', + 'test', + 'ABC=AeD; Domain=dunglas.fr; Path=/kevin; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly; SameSite=Strict', + ], + ], + new Stream('The response body'), + 200 + ); + + $symfonyResponse = $this->factory->createResponse($response); + + $this->assertEquals('1.0', $symfonyResponse->getProtocolVersion()); + $this->assertEquals('2.8', $symfonyResponse->headers->get('X-Symfony')); + + $cookies = $symfonyResponse->headers->getCookies(); + $this->assertEquals('theme', $cookies[0]->getName()); + $this->assertEquals('light', $cookies[0]->getValue()); + $this->assertEquals(0, $cookies[0]->getExpiresTime()); + $this->assertNull($cookies[0]->getDomain()); + $this->assertEquals('/', $cookies[0]->getPath()); + $this->assertFalse($cookies[0]->isSecure()); + $this->assertFalse($cookies[0]->isHttpOnly()); + + $this->assertEquals('test', $cookies[1]->getName()); + $this->assertNull($cookies[1]->getValue()); + + $this->assertEquals('ABC', $cookies[2]->getName()); + $this->assertEquals('AeD', $cookies[2]->getValue()); + $this->assertEquals(strtotime('Wed, 13 Jan 2021 22:23:01 GMT'), $cookies[2]->getExpiresTime()); + $this->assertEquals('dunglas.fr', $cookies[2]->getDomain()); + $this->assertEquals('/kevin', $cookies[2]->getPath()); + $this->assertTrue($cookies[2]->isSecure()); + $this->assertTrue($cookies[2]->isHttpOnly()); + if (\defined('Symfony\Component\HttpFoundation\Cookie::SAMESITE_STRICT')) { + $this->assertEquals(Cookie::SAMESITE_STRICT, $cookies[2]->getSameSite()); + } + + $this->assertEquals('The response body', $symfonyResponse->getContent()); + $this->assertEquals(200, $symfonyResponse->getStatusCode()); + + $symfonyResponse = $this->factory->createResponse($response, true); + + ob_start(); + $symfonyResponse->sendContent(); + $sentContent = ob_get_clean(); + + $this->assertEquals('The response body', $sentContent); + $this->assertEquals(200, $symfonyResponse->getStatusCode()); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/PsrHttpFactoryTest.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/PsrHttpFactoryTest.php new file mode 100644 index 00000000..b47cefc1 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Factory/PsrHttpFactoryTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; + +use Nyholm\Psr7\Factory\Psr17Factory; +use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; + +/** + * @author Kévin Dunglas + * @author Antonio J. García Lagar + */ +class PsrHttpFactoryTest extends AbstractHttpMessageFactoryTest +{ + protected function buildHttpMessageFactory(): HttpMessageFactoryInterface + { + $factory = new Psr17Factory(); + + return new PsrHttpFactory($factory, $factory, $factory, $factory); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Message.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Message.php new file mode 100644 index 00000000..0cda6fcb --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Message.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\MessageInterface; +use Psr\Http\Message\StreamInterface; + +/** + * Message. + * + * @author Kévin Dunglas + */ +class Message implements MessageInterface +{ + private $version = '1.1'; + private $headers = []; + private $body; + + public function __construct($version = '1.1', array $headers = [], StreamInterface $body = null) + { + $this->version = $version; + $this->headers = $headers; + $this->body = null === $body ? new Stream() : $body; + } + + public function getProtocolVersion() + { + return $this->version; + } + + public function withProtocolVersion($version) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getHeaders() + { + return $this->headers; + } + + public function hasHeader($name) + { + return isset($this->headers[$name]); + } + + public function getHeader($name) + { + return $this->hasHeader($name) ? $this->headers[$name] : []; + } + + public function getHeaderLine($name) + { + return $this->hasHeader($name) ? implode(',', $this->headers[$name]) : ''; + } + + public function withHeader($name, $value) + { + $this->headers[$name] = (array) $value; + + return $this; + } + + public function withAddedHeader($name, $value) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withoutHeader($name) + { + unset($this->headers[$name]); + + return $this; + } + + public function getBody() + { + return $this->body; + } + + public function withBody(StreamInterface $body) + { + throw new \BadMethodCallException('Not implemented.'); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Response.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Response.php new file mode 100644 index 00000000..a8907929 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Response.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; + +/** + * @author Kévin Dunglas + */ +class Response extends Message implements ResponseInterface +{ + private $statusCode; + + public function __construct($version = '1.1', array $headers = [], StreamInterface $body = null, $statusCode = 200) + { + parent::__construct($version, $headers, $body); + + $this->statusCode = $statusCode; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function withStatus($code, $reasonPhrase = '') + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getReasonPhrase() + { + throw new \BadMethodCallException('Not implemented.'); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/ServerRequest.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/ServerRequest.php new file mode 100644 index 00000000..88ec9843 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/ServerRequest.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriInterface; + +/** + * @author Kévin Dunglas + */ +class ServerRequest extends Message implements ServerRequestInterface +{ + private $requestTarget; + private $method; + private $uri; + private $server; + private $cookies; + private $query; + private $uploadedFiles; + private $data; + private $attributes; + + public function __construct($version = '1.1', array $headers = [], StreamInterface $body = null, $requestTarget = '/', $method = 'GET', $uri = null, array $server = [], array $cookies = [], array $query = [], array $uploadedFiles = [], $data = null, array $attributes = []) + { + parent::__construct($version, $headers, $body); + + $this->requestTarget = $requestTarget; + $this->method = $method; + $this->uri = $uri; + $this->server = $server; + $this->cookies = $cookies; + $this->query = $query; + $this->uploadedFiles = $uploadedFiles; + $this->data = $data; + $this->attributes = $attributes; + } + + public function getRequestTarget() + { + return $this->requestTarget; + } + + public function withRequestTarget($requestTarget) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getMethod() + { + return $this->method; + } + + public function withMethod($method) + { + } + + public function getUri() + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getServerParams() + { + return $this->server; + } + + public function getCookieParams() + { + return $this->cookies; + } + + public function withCookieParams(array $cookies) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getQueryParams() + { + return $this->query; + } + + public function withQueryParams(array $query) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getUploadedFiles() + { + return $this->uploadedFiles; + } + + public function withUploadedFiles(array $uploadedFiles) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getParsedBody() + { + return $this->data; + } + + public function withParsedBody($data) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getAttributes() + { + return $this->attributes; + } + + public function getAttribute($name, $default = null) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + } + + public function withAttribute($name, $value) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withoutAttribute($name) + { + throw new \BadMethodCallException('Not implemented.'); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Stream.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Stream.php new file mode 100644 index 00000000..06fff284 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Stream.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\StreamInterface; + +/** + * @author Kévin Dunglas + */ +class Stream implements StreamInterface +{ + private $stringContent; + private $eof = true; + + public function __construct($stringContent = '') + { + $this->stringContent = $stringContent; + } + + public function __toString() + { + return $this->stringContent; + } + + public function close() + { + } + + public function detach() + { + return fopen('data://text/plain,'.$this->stringContent, 'r'); + } + + public function getSize() + { + } + + public function tell() + { + return 0; + } + + public function eof() + { + return $this->eof; + } + + public function isSeekable() + { + return true; + } + + public function seek($offset, $whence = SEEK_SET) + { + } + + public function rewind() + { + $this->eof = false; + } + + public function isWritable() + { + return false; + } + + public function write($string) + { + } + + public function isReadable() + { + return true; + } + + public function read($length) + { + $this->eof = true; + + return $this->stringContent; + } + + public function getContents() + { + return $this->stringContent; + } + + public function getMetadata($key = null) + { + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/UploadedFile.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/UploadedFile.php new file mode 100644 index 00000000..f58a4bd6 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/UploadedFile.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\UploadedFileInterface; + +/** + * @author Kévin Dunglas + */ +class UploadedFile implements UploadedFileInterface +{ + private $filePath; + private $size; + private $error; + private $clientFileName; + private $clientMediaType; + + public function __construct($filePath, $size = null, $error = UPLOAD_ERR_OK, $clientFileName = null, $clientMediaType = null) + { + $this->filePath = $filePath; + $this->size = $size; + $this->error = $error; + $this->clientFileName = $clientFileName; + $this->clientMediaType = $clientMediaType; + } + + public function getStream() + { + return new Stream(file_get_contents($this->filePath)); + } + + public function moveTo($targetPath) + { + rename($this->filePath, $targetPath); + } + + public function getSize() + { + return $this->size; + } + + public function getError() + { + return $this->error; + } + + public function getClientFilename() + { + return $this->clientFileName; + } + + public function getClientMediaType() + { + return $this->clientMediaType; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Uri.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Uri.php new file mode 100644 index 00000000..f11c7e5b --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Fixtures/Uri.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\UriInterface; + +/** + * @author Rougin Royce Gutib + */ +class Uri implements UriInterface +{ + private $scheme = ''; + private $userInfo = ''; + private $host = ''; + private $port; + private $path = ''; + private $query = ''; + private $fragment = ''; + private $uriString; + + public function __construct($uri = '') + { + $parts = parse_url($uri); + + $this->scheme = isset($parts['scheme']) ? $parts['scheme'] : ''; + $this->userInfo = isset($parts['user']) ? $parts['user'] : ''; + $this->host = isset($parts['host']) ? $parts['host'] : ''; + $this->port = isset($parts['port']) ? $parts['port'] : null; + $this->path = isset($parts['path']) ? $parts['path'] : ''; + $this->query = isset($parts['query']) ? $parts['query'] : ''; + $this->fragment = isset($parts['fragment']) ? $parts['fragment'] : ''; + $this->uriString = $uri; + } + + public function getScheme() + { + return $this->scheme; + } + + public function getAuthority() + { + if (empty($this->host)) { + return ''; + } + + $authority = $this->host; + + if (!empty($this->userInfo)) { + $authority = $this->userInfo.'@'.$authority; + } + + $authority .= ':'.$this->port; + + return $authority; + } + + public function getUserInfo() + { + return $this->userInfo; + } + + public function getHost() + { + return $this->host; + } + + public function getPort() + { + return $this->port; + } + + public function getPath() + { + return $this->path; + } + + public function getQuery() + { + return $this->query; + } + + public function getFragment() + { + return $this->fragment; + } + + public function withScheme($scheme) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withUserInfo($user, $password = null) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withHost($host) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withPort($port) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withPath($path) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withQuery($query) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withFragment($fragment) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function __toString() + { + return $this->uriString; + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Functional/CovertTest.php b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Functional/CovertTest.php new file mode 100644 index 00000000..137a1541 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/Tests/Functional/CovertTest.php @@ -0,0 +1,234 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Functional; + +use Nyholm\Psr7\Factory\Psr17Factory; +use Nyholm\Psr7\Response as Psr7Response; +use Nyholm\Psr7\ServerRequest as Psr7Request; +use Nyholm\Psr7\Stream as Psr7Stream; +use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; +use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; +use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Test to convert a request/response back and forth to make sure we do not loose data. + * + * @author Tobias Nyholm + */ +class CovertTest extends TestCase +{ + private $tmpDir; + + public function setUp(): void + { + if (!class_exists('Nyholm\Psr7\ServerRequest')) { + $this->markTestSkipped('nyholm/psr7 is not installed.'); + } + + $this->tmpDir = sys_get_temp_dir(); + } + + /** + * @dataProvider requestProvider + * + * @param Request|ServerRequestInterface $request + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $firstFactory + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $secondFactory + */ + public function testConvertRequestMultipleTimes($request, $firstFactory, $secondFactory) + { + $temporaryRequest = $firstFactory->createRequest($request); + $finalRequest = $secondFactory->createRequest($temporaryRequest); + + if ($finalRequest instanceof Request) { + $this->assertEquals($request->getBasePath(), $finalRequest->getBasePath()); + $this->assertEquals($request->getBaseUrl(), $finalRequest->getBaseUrl()); + $this->assertEquals($request->getContent(), $finalRequest->getContent()); + $this->assertEquals($request->getEncodings(), $finalRequest->getEncodings()); + $this->assertEquals($request->getETags(), $finalRequest->getETags()); + $this->assertEquals($request->getHost(), $finalRequest->getHost()); + $this->assertEquals($request->getHttpHost(), $finalRequest->getHttpHost()); + $this->assertEquals($request->getMethod(), $finalRequest->getMethod()); + $this->assertEquals($request->getPassword(), $finalRequest->getPassword()); + $this->assertEquals($request->getPathInfo(), $finalRequest->getPathInfo()); + $this->assertEquals($request->getPort(), $finalRequest->getPort()); + $this->assertEquals($request->getProtocolVersion(), $finalRequest->getProtocolVersion()); + $this->assertEquals($request->getQueryString(), $finalRequest->getQueryString()); + $this->assertEquals($request->getRequestUri(), $finalRequest->getRequestUri()); + $this->assertEquals($request->getScheme(), $finalRequest->getScheme()); + $this->assertEquals($request->getSchemeAndHttpHost(), $finalRequest->getSchemeAndHttpHost()); + $this->assertEquals($request->getScriptName(), $finalRequest->getScriptName()); + $this->assertEquals($request->getUri(), $finalRequest->getUri()); + $this->assertEquals($request->getUser(), $finalRequest->getUser()); + $this->assertEquals($request->getUserInfo(), $finalRequest->getUserInfo()); + } elseif ($finalRequest instanceof ServerRequestInterface) { + $strToLower = function ($arr) { + foreach ($arr as $key => $value) { + yield strtolower($key) => $value; + } + }; + $this->assertEquals($request->getAttributes(), $finalRequest->getAttributes()); + $this->assertEquals($request->getCookieParams(), $finalRequest->getCookieParams()); + $this->assertEquals((array) $request->getParsedBody(), (array) $finalRequest->getParsedBody()); + $this->assertEquals($request->getQueryParams(), $finalRequest->getQueryParams()); + // PSR7 does not define a "withServerParams" so this is impossible to implement without knowing the PSR7 implementation. + //$this->assertEquals($request->getServerParams(), $finalRequest->getServerParams()); + $this->assertEquals($request->getUploadedFiles(), $finalRequest->getUploadedFiles()); + $this->assertEquals($request->getMethod(), $finalRequest->getMethod()); + $this->assertEquals($request->getRequestTarget(), $finalRequest->getRequestTarget()); + $this->assertEquals((string) $request->getUri(), (string) $finalRequest->getUri()); + $this->assertEquals((string) $request->getBody(), (string) $finalRequest->getBody()); + $this->assertEquals($strToLower($request->getHeaders()), $strToLower($finalRequest->getHeaders())); + $this->assertEquals($request->getProtocolVersion(), $finalRequest->getProtocolVersion()); + } else { + $this->fail('$finalRequest must be an instance of PSR7 or a HTTPFoundation request'); + } + } + + public function requestProvider() + { + $sfRequest = new Request( + [ + 'foo' => '1', + 'bar' => ['baz' => '42'], + ], + [ + 'twitter' => [ + '@dunglas' => 'Kévin Dunglas', + '@coopTilleuls' => 'Les-Tilleuls.coop', + ], + 'baz' => '2', + ], + [ + 'a2' => ['foo' => 'bar'], + ], + [ + 'c1' => 'foo', + 'c2' => ['c3' => 'bar'], + ], + [ + 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', UPLOAD_ERR_OK), + 'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', UPLOAD_ERR_OK)], + ], + [ + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'dunglas.fr', + 'SERVER_NAME' => 'dunglas.fr', + 'SERVER_PORT' => null, + 'HTTP_X_SYMFONY' => '2.8', + 'REQUEST_URI' => '/testCreateRequest?bar[baz]=42&foo=1', + 'QUERY_STRING' => 'foo=1&bar[baz]=42', + ], + 'Content' + ); + + $psr7Request = (new Psr7Request('POST', 'http://tnyholm.se/foo/?bar=biz')) + ->withQueryParams(['bar' => 'biz']); + + $nyholmFactory = new Psr17Factory(); + $psr17Factory = new PsrHttpFactory($nyholmFactory, $nyholmFactory, $nyholmFactory, $nyholmFactory); + $symfonyFactory = new HttpFoundationFactory(); + + return [ + [$sfRequest, $psr17Factory, $symfonyFactory], + [$psr7Request, $symfonyFactory, $psr17Factory], + ]; + } + + /** + * @dataProvider responseProvider + * + * @param Response|ResponseInterface $response + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $firstFactory + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $secondFactory + */ + public function testConvertResponseMultipleTimes($response, $firstFactory, $secondFactory) + { + $temporaryResponse = $firstFactory->createResponse($response); + $finalResponse = $secondFactory->createResponse($temporaryResponse); + + if ($finalResponse instanceof Response) { + $this->assertEquals($response->getAge(), $finalResponse->getAge()); + $this->assertEquals($response->getCharset(), $finalResponse->getCharset()); + $this->assertEquals($response->getContent(), $finalResponse->getContent()); + $this->assertEquals($response->getDate(), $finalResponse->getDate()); + $this->assertEquals($response->getEtag(), $finalResponse->getEtag()); + $this->assertEquals($response->getExpires(), $finalResponse->getExpires()); + $this->assertEquals($response->getLastModified(), $finalResponse->getLastModified()); + $this->assertEquals($response->getMaxAge(), $finalResponse->getMaxAge()); + $this->assertEquals($response->getProtocolVersion(), $finalResponse->getProtocolVersion()); + $this->assertEquals($response->getStatusCode(), $finalResponse->getStatusCode()); + $this->assertEquals($response->getTtl(), $finalResponse->getTtl()); + } elseif ($finalResponse instanceof ResponseInterface) { + $strToLower = function ($arr) { + foreach ($arr as $key => $value) { + yield strtolower($key) => $value; + } + }; + $this->assertEquals($response->getStatusCode(), $finalResponse->getStatusCode()); + $this->assertEquals($response->getReasonPhrase(), $finalResponse->getReasonPhrase()); + $this->assertEquals((string) $response->getBody(), (string) $finalResponse->getBody()); + $this->assertEquals($strToLower($response->getHeaders()), $strToLower($finalResponse->getHeaders())); + $this->assertEquals($response->getProtocolVersion(), $finalResponse->getProtocolVersion()); + } else { + $this->fail('$finalResponse must be an instance of PSR7 or a HTTPFoundation response'); + } + } + + public function responseProvider() + { + $sfResponse = new Response( + 'Response content.', + 202, + ['x-symfony' => ['3.4']] + ); + + if (method_exists(Cookie::class, 'create')) { + $cookie = Cookie::create('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT')); + } else { + $cookie = new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT')); + } + + $sfResponse->headers->setCookie($cookie); + $body = Psr7Stream::create(); + $status = 302; + $headers = [ + 'location' => ['http://example.com/'], + ]; + $zendResponse = new Psr7Response($status, $headers, $body); + + $nyholmFactory = new Psr17Factory(); + $psr17Factory = new PsrHttpFactory($nyholmFactory, $nyholmFactory, $nyholmFactory, $nyholmFactory); + $symfonyFactory = new HttpFoundationFactory(); + + return [ + [$sfResponse, $psr17Factory, $symfonyFactory], + [$zendResponse, $symfonyFactory, $psr17Factory], + ]; + } + + private function createUploadedFile($content, $originalName, $mimeType, $error) + { + $path = tempnam($this->tmpDir, uniqid()); + file_put_contents($path, $content); + + return new UploadedFile($path, $originalName, $mimeType, $error, true); + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/composer.json b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/composer.json new file mode 100644 index 00000000..07ed9cfe --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/composer.json @@ -0,0 +1,42 @@ +{ + "name": "symfony/psr-http-message-bridge", + "type": "symfony-bridge", + "description": "PSR HTTP message bridge", + "keywords": ["http", "psr-7", "psr-17", "http-message"], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": "^7.1", + "psr/http-message": "^1.0", + "symfony/http-foundation": "^4.4 || ^5.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.0", + "nyholm/psr7": "^1.1", + "zendframework/zend-diactoros": "^1.4.1 || ^2.0" + }, + "suggest": { + "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + }, + "autoload": { + "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + } +} diff --git a/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/phpunit.xml.dist b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/phpunit.xml.dist new file mode 100644 index 00000000..43aeaa33 --- /dev/null +++ b/lam/lib/3rdParty/composer/symfony/psr-http-message-bridge/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index 4bbd51f2..8dd67fec 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -15,6 +15,9 @@ use Cose\Algorithm\Signature\RSA\RS256; use Cose\Algorithm\Signature\RSA\RS384; use Cose\Algorithm\Signature\RSA\RS512; use \Cose\Algorithms; +use Nyholm\Psr7\Factory\Psr17Factory; +use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory; +use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; use Symfony\Component\HttpFoundation\Request; use Webauthn\AttestationStatement\AndroidKeyAttestationStatementSupport; use Webauthn\AttestationStatement\AttestationObjectLoader; @@ -64,6 +67,8 @@ use Webauthn\TokenBinding\IgnoreTokenBindingHandler; * @author Roland Gruber */ +include_once __DIR__ . '/3rdParty/composer/autoload.php'; + /** * Returns if the given DN is registered for webauthn. * @@ -165,7 +170,6 @@ function storeNewRegistration($registration, $clientResponse) { $responseValidator = new AuthenticatorAttestationResponseValidator( $attestationSupportManager, $repository, $tokenBindingHandler, $extensionOutputCheckerHandler); try { - logNewMessage(LOG_ERR, 'RESPONSE: ' . $clientResponse); $publicKeyCredential = $publicKeyCredentialLoader->load($clientResponse); $authenticatorAttestationResponse = $publicKeyCredential->getResponse(); if (!$authenticatorAttestationResponse instanceof AuthenticatorAttestationResponse) { @@ -173,7 +177,11 @@ function storeNewRegistration($registration, $clientResponse) { return false; } $symfonyRequest = Request::createFromGlobals(); - $responseValidator->check($authenticatorAttestationResponse, $registration, $symfonyRequest); + $psr17Factory = new Psr17Factory(); + $psrFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + $psr7Request = $psrFactory->createRequest($symfonyRequest); + $publicKeyCredentialSource = $responseValidator->check($authenticatorAttestationResponse, $registration, $psr7Request); + $repository->saveCredentialSource($publicKeyCredentialSource); return true; } catch (\Throwable $exception) { diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index 32ae92d4..cd2b850b 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -1395,7 +1395,6 @@ window.lam.webauthn.run = function(prefix) { data: data }) .done(function(jsonData) { - console.log(jsonData); if (jsonData.action === 'register') { window.lam.webauthn.register(jsonData.registration); } diff --git a/lam/templates/misc/ajax.php b/lam/templates/misc/ajax.php index 4634518e..2db6062c 100644 --- a/lam/templates/misc/ajax.php +++ b/lam/templates/misc/ajax.php @@ -191,13 +191,12 @@ class Ajax { * @param bool $isSelfService request is from self service */ private function manageWebauthn($isSelfService) { - include_once __DIR__ . '/../../lib/3rdParty/composer/autoload.php'; include_once __DIR__ . '/../../lib/webauthn.inc'; $userDN = $_SESSION['ldap']->getUserName(); $isRegistered = isRegistered($userDN); if (!$isRegistered) { $registrationObject = getRegistrationObject($userDN, $isSelfService); - $_SESSION['webauthn_registration'] = $registrationObject; + $_SESSION['webauthn_registration'] = json_encode($registrationObject); echo json_encode( array( 'action' => 'register', From 2aabad9a3d29599acf063c46106a6950755116cd Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 1 Dec 2019 18:11:19 +0100 Subject: [PATCH 20/51] webauthn: store registrations, check for duplicate security keys --- lam/config/.gitignore | 1 + lam/lib/2factor.inc | 13 ++++ lam/lib/webauthn.inc | 136 ++++++++++++++++++++++++++++++++--- lam/templates/lib/500_lam.js | 13 +++- 4 files changed, 153 insertions(+), 10 deletions(-) diff --git a/lam/config/.gitignore b/lam/config/.gitignore index b6d3e26b..aad238c3 100644 --- a/lam/config/.gitignore +++ b/lam/config/.gitignore @@ -3,3 +3,4 @@ config.cfg /serverCerts.pem /pdf/ /profiles/ +*.sqlite \ No newline at end of file diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index 677e1ef9..038e35df 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -523,6 +523,19 @@ class WebauthnProvider extends BaseProvider { * @see \LAM\LIB\TWO_FACTOR\BaseProvider::addCustomInput() */ public function addCustomInput(&$row, $userDn) { + if (version_compare(phpversion(), '7.2.0') < 0) { + $row->add(new htmlStatusMessage('ERROR', 'Webauthn requires PHP 7.2.'), 12); + return; + } + if (!extension_loaded('PDO')) { + $row->add(new htmlStatusMessage('ERROR', 'Webauthn requires the PDO extension for PHP.'), 12); + return; + } + $pdoDrivers = \PDO::getAvailableDrivers(); + if (!in_array('sqlite', $pdoDrivers)) { + $row->add(new htmlStatusMessage('ERROR', 'Webauthn requires the sqlite PDO driver for PHP.'), 12); + return; + } $pathPrefix = $this->config->isSelfService ? '../' : ''; $row->add(new htmlImage($pathPrefix . '../graphics/webauthn.svg'), 12); $row->addVerticalSpacer('1rem'); diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index 8dd67fec..b0847854 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -16,7 +16,7 @@ use Cose\Algorithm\Signature\RSA\RS384; use Cose\Algorithm\Signature\RSA\RS512; use \Cose\Algorithms; use Nyholm\Psr7\Factory\Psr17Factory; -use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory; +use PDO; use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; use Symfony\Component\HttpFoundation\Request; use Webauthn\AttestationStatement\AndroidKeyAttestationStatementSupport; @@ -30,6 +30,7 @@ use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler; use Webauthn\AuthenticatorAttestationResponse; use Webauthn\AuthenticatorAttestationResponseValidator; use \Webauthn\PublicKeyCredentialCreationOptions; +use Webauthn\PublicKeyCredentialDescriptor; use Webauthn\PublicKeyCredentialLoader; use \Webauthn\PublicKeyCredentialRpEntity; use \Webauthn\PublicKeyCredentialParameters; @@ -91,6 +92,7 @@ function getRegistrationObject($dn, $isSelfService) { $userEntity = getUserEntity($dn); $challenge = generateRandomPassword(32); $credentialParameters = getCredentialParameters(); + $excludedKeys = getExcludedKeys($userEntity); $timeout = 20000; $registrationObject = new PublicKeyCredentialCreationOptions( $rpEntity, @@ -98,7 +100,7 @@ function getRegistrationObject($dn, $isSelfService) { $challenge, $credentialParameters, $timeout, - array(), + $excludedKeys, new AuthenticatorSelectionCriteria(), PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, new AuthenticationExtensionsClientInputs()); @@ -152,6 +154,22 @@ function getCredentialParameters() { ); } +/** + * Returns a list of all credential ids that are already registered. + * + * @param PublicKeyCredentialUserEntity $user user data + * @return PublicKeyCredentialDescriptor[] credential ids + */ +function getExcludedKeys($user) { + $keys = array(); + $repository = new PublicKeyCredentialSourceRepositorySQLite(); + $credentialSources = $repository->findAllForUserEntity($user); + foreach ($credentialSources as $credentialSource) { + $keys[] = new PublicKeyCredentialDescriptor(PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, $credentialSource->getPublicKeyCredentialId()); + } + return $keys; +} + /** * Verifies the registration and stores it in the database. * @@ -263,6 +281,8 @@ function getExtensionOutputChecker() { */ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSourceRepository { + const TABLE_NAME = 'lam_webauthn'; + /** * Finds the public key for the given credential id. * @@ -270,8 +290,19 @@ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSo * @return PublicKeyCredentialSource|null credential source */ public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource { - // TODO: Implement findOneByCredentialId() method. - logNewMessage(LOG_WARNING, 'FIND ONE: ' . $publicKeyCredentialId); + try { + $pdo = $this->getPDO(); + $statement = $pdo->prepare('select * from ' . self::TABLE_NAME . ' where credentialId = :credentialid'); + $statement->execute(array(':credentialid' => base64_encode($publicKeyCredentialId))); + $results = $statement->fetchAll(); + if (!empty($results)) { + $jsonArray = json_decode($results[0]['credentialSource'], true); + return PublicKeyCredentialSource::createFromArray($jsonArray); + } + } + catch (\PDOException $e) { + logNewMessage(LOG_ERR, 'Webauthn database error: ' . $e->getMessage()); + } return null; } @@ -282,9 +313,21 @@ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSo * @return PublicKeyCredentialSource[] credential sources */ public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array { - // TODO: Implement findAllForUserEntity() method. - logNewMessage(LOG_WARNING, 'FIND ALL: ' . json_encode($publicKeyCredentialUserEntity)); - return array(); + $credentials = array(); + try { + $pdo = $this->getPDO(); + $statement = $pdo->prepare('select * from ' . self::TABLE_NAME . ' where userId = :userid'); + $statement->execute(array(':userid' => $publicKeyCredentialUserEntity->getId())); + $results = $statement->fetchAll(); + foreach ($results as $result) { + $jsonArray = json_decode($results[0]['credentialSource'], true); + $credentials[] = PublicKeyCredentialSource::createFromArray($jsonArray); + } + } + catch (\PDOException $e) { + logNewMessage(LOG_ERR, 'Webauthn database error: ' . $e->getMessage()); + } + return $credentials; } /** @@ -293,8 +336,83 @@ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSo * @param PublicKeyCredentialSource $publicKeyCredentialSource credential */ public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void { - // TODO: Implement saveCredentialSource() method. - logNewMessage(LOG_WARNING, 'SAVE: ' . json_encode($publicKeyCredentialSource)); + $json = json_encode($publicKeyCredentialSource); + $credentialId = base64_encode($publicKeyCredentialSource->getPublicKeyCredentialId()); + $userId = $publicKeyCredentialSource->getUserHandle(); + $registrationTime = time(); + $pdo = $this->getPDO(); + $statement = $pdo->prepare('insert into ' . self::TABLE_NAME . ' (userId, credentialId, credentialSource, registrationTime) VALUES (?, ?, ?, ?)'); + $statement->execute(array( + $userId, + $credentialId, + $json, + $registrationTime + )); + logNewMessage(LOG_DEBUG, 'Stored new credential for ' . $userId); + } + + /** + * Returns the database URL. + * + * @return string the PDO URL + */ + protected function getPdoUrl() { + $fileName = dirname(__FILE__) . '/../config/__lam.webauthn.sqlite'; + if (!file_exists($fileName)) { + $handle = fopen($fileName, 'w'); + fclose($handle); + chmod($fileName, 0600); + } + return 'sqlite:' . $fileName; + } + + /** + * Returns the PDO. + * + * @return PDO PDO + */ + protected function getPDO() { + $pdo = new PDO($this->getPdoUrl(), null, null, array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION + )); + // initial schema + if (!$this->tableExists($pdo, self::TABLE_NAME)) { + $this->createInitialSchema($pdo); + } + return $pdo; + } + + /** + * Checks if the given table name exists. + * + * @param PDO $pdo PDO object + * @param String $tableName table name to check + * @return boolean table exists + */ + protected function tableExists(&$pdo, $tableName) { + try { + $result = $pdo->query("SELECT 1 FROM $tableName LIMIT 1"); + return ($result === false) ? false : true; + } catch (\PDOException $e) { + return false; + } + } + + /** + * Creates the initial schema. + * + * @param PDO $pdo PDO object + */ + protected function createInitialSchema($pdo) { + logNewMessage(LOG_DEBUG, 'Creating database table ' . self::TABLE_NAME); + $sql = 'create table ' . self::TABLE_NAME . '(' + . 'userId TEXT NOT NULL,' + . 'credentialId TEXT NOT NULL,' + . 'credentialSource TEXT NOT NULL,' + . 'registrationTime VARCHAR(11) NOT NULL,' + . 'PRIMARY KEY(userId,credentialId)' + . ');'; + $pdo->exec($sql); } } diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index cd2b850b..b51c55ed 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -1412,6 +1412,16 @@ window.lam.webauthn.run = function(prefix) { window.lam.webauthn.register = function(publicKey) { publicKey.challenge = Uint8Array.from(window.atob(publicKey.challenge), c=>c.charCodeAt(0)); publicKey.user.id = Uint8Array.from(window.atob(publicKey.user.id), c=>c.charCodeAt(0)); + publicKey.rp.icon = window.location.href.substring(0, window.location.href.lastIndexOf("/")) + publicKey.rp.icon; + if (publicKey.excludeCredentials) { + for (var i = 0; i < publicKey.excludeCredentials.length; i++) { + let idOrig = publicKey.excludeCredentials[i]['id']; + idOrig = idOrig.replace(/-/g, "+").replace(/_/g, "/"); + let idOrigDecoded = atob(idOrig); + let idArray = Uint8Array.from(idOrigDecoded, c => c.charCodeAt(0)) + publicKey.excludeCredentials[i]['id'] = idArray; + } + } navigator.credentials.create({publicKey}) .then(function (data) { let publicKeyCredential = { @@ -1428,7 +1438,8 @@ window.lam.webauthn.register = function(publicKey) { form.append(''); form.submit(); }, function (error) { - console.log(error); + console.log(error.message); + jQuery('#btn_logout').click(); }); } From a8738a5e5351aec25949aed30ee563d276a1ad2f Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 7 Dec 2019 12:49:45 +0100 Subject: [PATCH 21/51] typos --- lam/help/help.inc | 2 +- lam/lib/2factor.inc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lam/help/help.inc b/lam/help/help.inc index b8f44924..f3cc3c99 100644 --- a/lam/help/help.inc +++ b/lam/help/help.inc @@ -330,7 +330,7 @@ $helpArray = array ( "528" => array ("Headline" => _('User name attribute'), "Text" => _('The attribute (e.g. "uid") that contains the user name for the 2-factor service.')), "529" => array ("Headline" => _('Domain'), - "Text" => _('Please enter the webauthn domain. This is the public domain of the webserver (e.g. "example.com"). Do not include protocol or port.')), + "Text" => _('Please enter the WebAuthn domain. This is the public domain of the webserver (e.g. "example.com"). Do not include protocol or port.')), "550" => array ("Headline" => _("From address"), "Text" => _("This email address will be set as sender address of all password mails. If empty the system default (php.ini) will be used.")), "551" => array ("Headline" => _("Subject"), diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index 038e35df..3640ffed 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -545,7 +545,7 @@ class WebauthnProvider extends BaseProvider { $loginButton = new htmlButton('login_webauthn', _('Login')); $loginButton->setCSSClasses(array('fullwidth hidden')); $row->add($loginButton, 12); - $errorMessage = new htmlStatusMessage('ERROR', '', _('This service requires a browser with webauthn support.')); + $errorMessage = new htmlStatusMessage('ERROR', '', _('This service requires a browser with "WebAuthn" support.')); $row->add(new htmlDiv(null, $errorMessage, array('hidden webauthn-error')), 12); $row->add(new htmlJavaScript('window.lam.webauthn.start(\'' . $pathPrefix . '\');'), 0); } From 596b5bb1f8b6d53f634948f9d88c5065afe7aab1 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 7 Dec 2019 12:51:17 +0100 Subject: [PATCH 22/51] i18n --- lam/locale/de_DE/LC_MESSAGES/messages.mo | Bin 347936 -> 348859 bytes lam/locale/de_DE/LC_MESSAGES/messages.po | 4408 +++++++++++----------- 2 files changed, 2219 insertions(+), 2189 deletions(-) diff --git a/lam/locale/de_DE/LC_MESSAGES/messages.mo b/lam/locale/de_DE/LC_MESSAGES/messages.mo index aed2df73c82bf7f642a202ec73667c11810faed0..d3c5e0411f80c27a001912d48916beb72422e88f 100644 GIT binary patch delta 73543 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 U51!UR!6BTp(8%!Q0h>zyA3??nX8-^I delta 72619 zcmXus2i%X<|M>CGecu$ymW<*id(VuF+aB3_hLkN)BE=`8P(~_g8ifi;MMcv}X(&=O zzKJxfv}mB;>-9P3_y0W}|K~a9x~?tRD2hQ(<=v4X@R3f{z8*g4Y+iNp-7f=jU`et?ejJm$emN~I-o zVlliJ%U}_#g3d&%Sl$gUA>R-4;xNpIx8aqvpO{S|6IY>A{4RRqmskLgp;LAqufoep zhYA{CVe;*f6-x|3pSuO^=uEVui_i`&kLAx}QSz^2avh2NBue3otI`ssaV1`ayU?D0 zjE?Y2%z=l|hK{2Z|BIV3N13!lYutv-FlX7cL<_tYZ@`7<^*^vXUQ;eDnP}>Ta-qRJ zXmEXw8F)PAf5A-h*~$mYVlncq(J3Diof3Tzotbs$jBZC`;tRC?KcYD+Btt=o3gL#@ zco{cziTPpZ63mR{56AL#XhrX1CjJkdq5sg}y|`jnqAKVNHb$qs9lBIQ(IuLgBw?`4 zL>pR$PVGx*h1<}P?vCY0(OCEsZ76r8P+=|1B;OL9ksHwlrbXwW9exZ;;R{$2lb^ zfLVVMu1z;|#N*HhC!rNCz#H+cSYD=TTB0%eYtZYrV_AF-4X!=tj1{gH&Xt;I(AGmc zoQ3X+4k_7x{Ylv4QRu!*q9eW^t@ugwfZC2O&HLy`4x&?h9BuG#%!P%jhb1i;y#~GB z8y(mLv?Ft|g!_MSykR3+;hyL>=!_gk8~hzzg6uUyzBoE_RnZ38qaD2wjgisl8qY*e z#;4Kew?z+NGLwQ+Bn-m5HNyc@87q?Sj9$MT?bu_{XVHdVL4$T5Ix|0@GkFFZ;w81h zJEAqZR8z1Pu0aR%O)d7nLG}v;Hjq&}m>&(Ql2{+hVH+G2%b&q3$*)H{@?k9h8C`;5qD!o6!?2XM@o5 ztI!TtMF-FbeXa*O<-=lrQgkj_-{aA>$Zkj`UMJz&??roj2(#e{w8CG}3Nspph6MeghgK6VZ)$p{iDz&7}u2j@7ribELe#)^eVcY zcB1?IJ9O&*L(lm9S>cOsZZ=g%N18wI3rs_+Q zu%hGWr&qR?;k{lSE$jGqgiLqWAxcD=4^l0saJ~$kW{uwwGA3{f1phH;ua%e0xiuoSs z{i9<3PIPG=zy|m@I@9|xX-^K5u%{=`11D$4@ZpdLt+*Oi$JXeGZ$pD<8dkvv(GkCc z-hUWVBgHK8g*&AsnqY7A`omZO*LPz7JMu3m@PSikg}FM1hU%e9(G;EH)@aoBLOU=V zouNBpehC^2>(JfvZp?p+w(~dIPQEVT`6^x5|9%=ZqreM;(O|q2Q!kHr;|8?CoiYDE zbP0Y(@4L8bXy8h;ybd~}ZPDj$jOEj!51_%iI!VF`UPRY?7dpbvWBHFUe;z%#@^lLi zUV)ChJUSE2(KYXlo`fT@7VgK|n4^1mz7ZN@?a>Y-2a@nBcLdt=1!#~wh2Hoz+Oc2I z4qQNY!=*h!5EVhMmqp9#pi7m7eo1vfW2y(X#lA8B6w+QY@eBzocpYtEH>TqObVOgF z6`w?d^>6e%$mkh5P!oNw3EE%>bjrJ+_m4yeb_=@1v(XMdjk(ey@ejR8#yHJ7UtFwCkIe=z>$x zhCaq?@H2E~^7Ttg+>gc3?YI^#e+ix1H_&bO2^!4D&=Dv4hlY!z2BXCtAVb=mm7!78?*oTpvAUcUuBH|Cuf~Q+ z60YfZbZTx#J2D4d(`D%X-hkC{C#HfjUcZ2@Y2HD>E71;Dj`@~oJ>AgY8yd?;U~2#0 zM#AWwj#KbKyc_cj4omS6+Og&6+P{FV`77viyJP-qGzLzi1IRTbOnoVI$(o|&1JD87 zf|cC=cao?`!814uKSigs*Y#oK{V4;@iy?23)hrC5O8_cXfAHlQ7Q4?RhbVhz0H#_%T} zjnJhVj&*Pe8k`?uY4`sr5>9RYVPOr6pleeO4Yr!Gybao+p6JNNp}}=m%s+^Za3$Ko zH__PG9rFj!-E|c0_^+6BL^+3tk(EFnsEt<83=Pgs@p>;bh(q6R zpu1!%THhhG<0sJpUKq~)??WQzh*0tM=!N0v$S0sZo`Y8K5L)pHwBs+M!TDY+{{lV0 zenw;C7j!_mMuuQ4g2vj_==K{tG8tYbODJee!A`Wnj8SQ+10ydwRW;Cqq;1R(Lq{?V z%iuz^p^a!qx1mA1CzgMUS>#V*Wvn(jd|~xVlCa__=+r%ex8ZVp0WTkumUszwVO5+m zHdOp9x(#1LXYfNDjAd^MZ^3)ef!vFBE*{d#nS8_|Xjq4%Fgm+S)Cv4Z1M=Rh)1l7ugl3TOqj z&?&5scAyoy-^ZfSJ{O1J6KIDnpbcC!Asj4)&?UPPyQA3f>Xqf6C2ULTGw#cf!@{Xd&T z>L5WIcn__3589D$;`I};Jln0|C@p~QmWpUYb}?0-i-ih`@~R`h{K(DGGS5nn*3_+a!f+Td}t!N0LLX1hHtaUTvq z8~6#`-g%~krKy6>WGyr%8YW3NvQB7}4??H-4zz)X(2+ca-uE0@!4`C_x5e`J&=c<< zIx~M@JG}Ug@bc=4mOqU?_X;|o_{J6z%XW=>5~t4$a4$zW-N{u!6N{!>`2rE_5mO#`5E6 z!{^Y3F1agAVF9c_zBn4yEz#I%hxM>KX5oEk5PpbF@Gq?E{;xMZd@zi~>g1Q=0Q?w- zV&xfWiP^Xiuf@w}h94k?VqNk}@E-gK8)A=HX^AIrA=bbucZVPzh;Gx_(e0T0i-P}1 zoWZ~D3BQ;7Wp-G*7IVVy?HJB!a@vwK7SI6CsW_l4LQiz~@L zgDtVcytKpyTr`jUKaE85`JsWWxSD*I1!;+Gcoy5^mW5$vvfm%3x-MGLbyyOgMwete z&cog4QuSUGw(~G_X>LK6baHgjBG%Gvu!aJU%9rDf?_nSEA7OJW_dxj5uaW4KZbp~t z7}`*=2h$QGup$~u51=vdBpS5Opr3AU#{3uPOrA`VuxCXc3ft#Ow8HvmFt$ObvJYC} z{n5wJ3Rhwed<*Mf!NsATwpcKY=tuYaq=!S)Pelj12(2%2Y+MJ&o4;0y+a5(fW3y?d(UMPbQ9%SVF;XXtdw^ zNcgkeQ|Q^=_|fn|atD?nzc+dojs7bh3ro`tU7Dw`8Sca9@v_H5FmK1M-6H2PaSAO6&QAQ~HYp=bRnwBDDa+oSu??S2G}{u9r$|J`P1 zC~(RzULT^jH(EXvEx#4*$c}jZ92)&OUI-Olh84+|z$(}_IuXm0e+u1pyRk5Siw^iq zlEgR?IW~j}C!;ssjgI6oG?<=6NAx2)wP(?wyXeJGo`sFbcR{!7e6)czXoGK{FR6X_ zDxO1UBDwLUFyc4S{rNsRHJ_o|>woAzF7$G!s3^LIHSlt5jV{G?F+Uky+xyWDJdMuW zCbYqm=z(+wiH&6990^C9Z)50TSu|KWp~2G=y*?5h`Lvjyk9OoKbf#WI8+sRg{sNZg z1kApP4;8HXO6XAcSHm{F5r221KS#n5Klxg?;Td$lZbmEo6RTs@&Ea>q1F=5&c~}#7 z;N563)O!=!0LN zQ+ot!;os<5R(U6Mvcw-@dB&uGJE(C71SW&eBQm0QCD z<@zHmKccabu`SF%0kp%Fu@p8&pBsdB=yo(1@86aT z4?IeN54?o#f*okb_MkIx5dE~ebbAQKHke6%09x@>^tmPITkt7##A{;ttLQG;hSs|) z`e~9xH445$N04hr*w01LeOwWJpf%c17wnCrumWyFW9K+JwHZ6Z$yox8na1ezUC|Ep zM>{wIZ7+HA|0kjg(Czdj+TdDrs&}GGb0A*-9-YG9(53np9bw6LLqlcIdTK_SqT8|~ zT2Bw;giR&}lc++$c(jL0(FfOJIoyoR@DO?emfjUcR12N*M(Bvzps~^!4c?w;J)_Zj zZ;#%CcHqHOp8dCsggsw}j`T&$!X4-gWPdL_P!g@EGP=#`qa*5yHq-~Lco_Qbm=Mcn zVj1#_(4}|a; zy2jt5vG6-u|HZpQhYMk{JO!mmxR2YTGcgU_4KvY^EJhEa)#!+ips(j+=uG7NAXHo$ z%~wZ*w=KHeZbV0XCtBZZw4;xF!2b8ZSWbZzzl-+tBedt=p;LPrJK_a&1RXvMGtn1~ zi5t7Fi;a||DKKmj2-xG6p%-pKM>su}FGnl*8m;&@ zbY$5+4zW@k?f3w6gg2x0Oh-HL5W0jgWFoBn+Zj=u9+6 zA8e04*cF|T0ay`dp}S-~x+E{7&%KG>zZ31iCs+%=MxW37S@`+C9y-vWSlInPo`em~ zK~J!SXonVK2ChQ)_j5773vK8FG}t~z*Y-#3j;GNETJ8@s)(u^nA!zw+=<~BM_2>T! zNf<0E(VoAGj%+(R(vQ$7-itNyIQmg~<>%r40qE3@K-YXCj>bi?{5;yhTwlbgN0+L? z7wms8G^D_uwLsUnD;g|=(d$#uk=}!LYzZ3WE6|EI#p}D!fgC{3mE-70^B)M$U4iDy zq3zW?P(EB}8gFQaHrPGhH~?*E_=D4{6-*kI>nL%oJJ5acX}t3^`sBap9A-Nhx||Q~ zaw)XQ%4j_e(EGch_xD4GG7P|?wyFv@n$TE@1YI;gbwHoI!Bj%6@Cw&iALX5UvX9%mdz;eB%kcvV4r~@#iE7E2#5Lc(65^zYZPA z%`ty}^eJ>?8_}rPhDOnTbRa*(@-s1?^M9eEMbZ0fU@L5aZ7?~PgbjX*PSFu`=byq# zcm|!?Qs0LAYM>)&j*f5)TJhb{N1|)ddf!5$?Gtp(zehWI5_v9}I8Va0&37nR39FEA zi8e4kdN110)94Jmf_7{N8bkZgihqj!g?1pvcVWX9Lzk{3I?#%#a?YwcBs^jophwkw z^uY(wBWx8qf_3Nyd+EVd)x3di6=?1ksBHg-P}1~eO+l3$M1X+Lp@ zgbn2TKAaF`(6w)dRyYK+a4mXvoJ8jN*gc~^9M_m9^{pfNKE zldj1e5>~tj-M5P|9ao?au0*Hq#aRA2`Xk6&=#h2^-6iL-5!U}9EXhQyL;hZL%HKxY z+l>x*-w*8nOcFm*V9@3GF^n`HI<;NVH60oAx1&8@gf7)nXob&4Uqm~+1#Ms#8XI3> zbv%tem-$nOp>jVZ!vl3Fu)$_%gMDLuB)SWxqcO1#$KqRP!&QDxOAVkdIzzqC1_q-u zISh@V324yXjt+D-`W9N4B;nLN6mM9DPU-V#u;e&WxEdMQ*C(p-3&f}qnm!TuL3d>+cw1G}&MSak3of~5L9kKjwOf5~! zKZVZFdi1D$6`kSz(UVBkGyb0yD(DQ}H#x9g%aqt9Y#%0EJ*`ZU^5_VZyT>Y__>eRK|1BflQ2;y1DUk_#ay zYobSTTQt~{Q%Piz*n+k2Uvvtq{TF(8BQ_?#BKj>lQ&;ka@XMwin(v5ix8Z0#v(bIO z1v}#bbO|e@rKdX39l4%N%pu_jR->O>AEHxq99^TP>FKGzWH|(#p*2_$_n-~_jpeau zM!3Hjy5>Wp^U!U(3BB)N%%^2bXC{fFvLxzrp(i@k3(yB%jQRc1zhZgu?6G5L$A_Tz z&&Et#iyiPo?1)7#3gyGm0nI@>zQ%IePwb8tj$1%J_r<|#=#8DxgJWFG&x_aBp)>Lk zI<>!{GjnB*FavF{GWna)hMzz?`WAZMVN6=_r8&dME28-hXta(*ADo3Yv^wTLKr1+j z&cNll(o?&mG1`&7=n~9Aqx|`p{~29^0+*zxg0JBv>EYi>OrpR8=TUS!y@THPD_UXE zOVh(QA=<(5=*%oYgL6IFkrU`PEs#4swF|1D&kw{*oD<8RLu2hg?qrC@vlJNhRq~{# zzSV}J`6cMf0dSCH;VePwOd-6-sCHNWLwwL8kPc*|;n1!>G zB;1eh;V3+h2XI7z^u#c1Rxm{Wv*=R%ik_GS3xyGMLuYI}HpQpVAU=$4zwDQVj$VbW z$xlL0+PBadNY*JF)~Gu^LBT>?f>kb0Pc+9JXwY0-BurstOvMN~)g#f9@@~8x^Arth zI|tj6--&iOPqFmWZxlLW3-V`>B}^t77Ee$8<;;HAhYN?$DQ$2?I7se8r}{0l=LN0| zQ#c1#kl&59aZHIYrOUA%`R}kD7A+Zqc_{jkIt}l~&G?A>zkOy{qa!$%3wcY03LZtL z^i#CQ`Adh9^h7Iq2rb`(PIbYn(o??+t%-JM2HJsF(5X(82{BR<&G*Faw4b<@L?e6; zUE}O!Ly(n6x6yTI1G8iKs_57F8s!Dcg#mnsndJY%)WK6e1l@4_hVmt7P*1Foo|up8 zFzHk^tQgjK0`?;RFd9@Ru_czMl%9GW55+d*7vn(u23f8m?T^mP44i;(qStFw z39-=yKP0~!GjT~(_P-yMuT@P?eLU_!AJ~uC@lVW#|DqofiE8Po502cJmuz8lq~&7% zR`d&K7W%e)5Uu}N^c!#^`bD%03*xuclIf|B%-<=7!8*^si zvDRn@?nHY&AFW_5TEQlCD&Is${s}te-=M*FGG0%w7h>l!w7eP?#un&|_N&MKH*pgM zHZTLNcpefQiDl>%u8#SQ=*#H!=(lJGGwO%nDiv)U?TZd*JuBG;Cw-W zyWlLE&(|PKX$`czHCn+SbY>=@(L4vs<4SZ1K14fo7@d(s!|>&l4;^`3w4M%Vy*DID z_`prN`e-3>YY(Up)FFG^dp=BF`ri14y`CMxjxA8@k5R(Fa$dGxRFDOZKA;eu<9oXEb*HMmv_#B;1!1tv3&P zUomvaD#h!qF}wT!S`ryt=!#a<6Ya<#bVf$S@>|djOhqf4fgaHhpzre+FtzQ_5gv^B zLuf~iqa8nscI2X_>>l@jX%enYb@YZTw1LjiLGk)Hv_p5HBb|$G$AxGImY_lQ3c55O zq4j=+4(vqCpFv~g;w<*R`?nYgr@B1)U<35QHfV?Xpbrd3J1_;EiJ53j%)wgtG+N*1 z(Ie>dr_p-C!%Z7inpN8y@z(- z2)Z<<(Iv^Y+h;J-SQo zK}YsjbanJ4Y)ScBF`v;gENxzNhD#<%IKmpyrszmIq5FR%+M)Z<5iLcJ($~?o{v`Ti z^a45~m$eGluR`l@h?cj*X4o6MVe(NDeMp?Z5!kAAxN$u?mg zrO^Ab(EEC#&yU0=m_%dfEgX&quncCk3m>;PVqN$D!z4VZKE$q=y?t2g-e^x3;I;S+ zTEXw=R2I25{CrR@It={=T#b$KXLJV3cL>|B8XDyF(HZQ5`Q86RN%#_(h_&%abbEb@ zPW|WT2#=yu{5RTxoE^g&UygRP8hWs_!CE*pUVj+9|9Q0D*RdtOgGmdrcM97i7aEn9 zp=($!me)t8vPHZ;5J!@~2_5MHEQ5tRr>Fjs_chpv{3B??`_Y*=f-d1PY>Q_*v;R$G zbqPOU%*E>DUq>rEj$<)j*YNc_4gEm*4hP`n-NHV;6`jHLSPwr(XEIOs@PkTotVn)2 zI`ZAv7xVUD|C<=uBR%zNvF*5!e9oTXlj~7Tz0I&DzUhgXcqB>U9umX*rKkQH>M5K=er*3RMMv>I^3?`} z5pKcK4Q`EB@_8cc(4>j zE2x7;dlR(cZs-VxV{4p%R=gQY;ydWu?+|+bfAM;uAz>zpqYXB|JFqPp3p=o;`~MV) zsuW~i9}bXC=nXeS7hnbQub~Zmhi4Udh+!}pC5;A<7t=;7ozWuhcE}e zh|csDOnv|FAYlxAjRwgv^ug0;$Noeg$aQ1rP@!l^+(3CHblZJ}gRtPR^weMAzXiLH ze-AA$Iy}78ZbyUk@!{-$r+Ra|a0HEo>?6W~lZoTWH^kn!4t*Np25mkaBOI>DSF>{v?GhK3T{B_`v#4*{5Pei{(4-~BnhWz0XD&J(fwUwTo_q1 z^gVw)y4JU&XZwnH{WJ8X^C#MH{hLFF`k+BO3oGICcpV-=M7_hEnBj&`8rEup+A+VCWF37$eb^eMW8{~+xp6Ia|C z)~-Ezz}ywxi^Iv6nHV0NhtAL@9E5+M+pNcJ;iSA7eRpieI`}h|!4i|g^%m$$Y#us; zt8ujZ{~!rR+G=v>*#vA#{vm9EpP^@ak=w)6_CjZ91{%dH(2Czim*^nYz_cl$qqWeW zegK`R=W!tByn`RXXg@KWgbgl0r}|^GqFi@|wXKEbC!$OBB--FsY=vLpI4m_ae5fo! zXXYC$iGQOVDmpDJNqg)_ej+BlaXSg8>RYs-Z0riltDqxoiw5Oz9D?_v6`w#maOqtk zn!BOTuRyQAkMr?oycZ`;4@Z0YjIacSXR!Ybx^@)kB+SHBmwi zU;;X_chGmkujn?bKPx@43ddjrEO2*vA`3g9pYik2?YrY{M&R0fM}bp%4l}XvJz?tW zp(C7vM)Nz^5|5)Dsxdn~aVO5iPMDYzmY_3sAp1C4{u6qCy}99JycgZ}-zG_T!qvDp zjO;12Bd=pV+#mBlpdCou7k+N9jAhAoL#KQ)8jQ=)ir+$aL*9A8rs(x?cpuJ3Pt0V| z`JsZ&=zg9Q^Dm$ce1RU(=g}!EzaV`3wZw_!*Q5Ko%)$`79Wj&qT=ZmIgVysoI)HQN z_AGyYY9RdlPr@5#q9cC+4Yv1V{sh|dix!2GunZcU*Pubx8$Dnqp(9?6jqr2yv%cU1 z>8XE%)d72u-+=uw`-AEH2RxJf{*HtVzKi|vXUxKu4~28#ZuG!-DS8y0x+06i$U9+U z@{`c*xe0x_97G57D|&=q@^HwPMhDagOVWO#9|^zTr=Z*LL3FBLLp!z~?Z98y6DuzX zQ9m8ak$(xDk%Q=ce_~Ut@JQ(B2=r~Z6rG_@uo)&EW&gXj%}My+7<6RIFx4=+o&LgS zu*zfUiT~keco1KHJlr?`i4b%fa3SSsPllOXgx`}tfp+NArD1pEUzVQwCyfJ_vHzX2 zV-(o4Jj=t#o1i1@kM8dm(QSGdjrQ}{5X-Izu`?JQz&x~}xA9v14|n3VPlfk>{-;B1 zypGpU{@2s&|GP=lSs6yK1-p~KWL21nL1@%2Mx*?l=+9_F1y+ZOx}hCffLZuHw!>V{ zgx`epz&ptAiuo4LhM;{VNh0-{Mc2H*bD_fOXb_FY`*9{V!9r`oecjO+yBVFK<k z6E1yiI8hs*FR|e`7T2OnS#(|470In6434tThY|M1UgYmWx8)Z&1vAzY#c6z0;$7qy zzYv})ydj($ZE+~&ccC5o9-qcSFNT4_Wcm#;_!};V|+ma2)2?6i(8~=n@`751J;ggqPMW=o0QnJ8%JOyZ_6*8gA@| z2HisR#-r%k=XouxRReU}j6f@1gT~IcXoWX!jxQH%Kz;{00~gSaKefdJhmTRT<9%LF zPkciAiML5u!ToQ9p1y`2nZ@1=9m&Eh@*~kHT!TjUZuGvwZ-oXL;Xv~9(Ra#m?1uy2 z4lk+K(37(6JK^PY117gq@FI!ZapKnW#0>le%}>~tp7;_^ph3E8J3Yof&>-8nBXs;6 zx&&o*hNJZ%TtWUcj==}s4eyZu(50!kEBy33br<{J9;Lk(rmO~f!zgqq-a>apq4&c` zYhqLKz0tFL3HHSkI1gLz4oC4mJV!qN2jLx%=fiNawuz3xBV1qfA^YFN#E(M7eLoIU z{V3Ydm*{RNwkJI?7{}uv+>3W&txrPv3+M+-zE8vTCTK7(MQ3ggw!(AhQQv%TxW9jr zgh%jPtc@>Y7XFCMvBJKve@A0`@-Lug{y*qamHI6FN_Hi-CVv9$Snd5`02|QP`e`&a zDtw-vcoOfy&X_FoMfjocR?JJmR4j$_F&n;sxo{&E!?&>veu+8pKfD-o9tid1$L!>b zVLE1FCSHZPv1Pp8Ey#Z>heUoZTo-S+8GSuYLmPeq3*$3b8sEZ8@H?~vC*$?AXh-rK z3>`0w8RUzj4VOfNwklqRH{fvh{}d87bQoviNwfpwzYM{*5QmaqhYK*@SK;Gx8QQ=} zbgh5IqFDOt5X_D6D)P6XGxr#}9XDYo%=Zl+xwM}cM8X@Vp|S83TG5B-LG%L}G2 zb}Y}~F!Bm$1I^I4;sEsiThW-9i#EIt-Dca-0p&arI#3H8c@J!gH=}RU4M&pU&t!g~ zz+gIqMt$P@@Mk&M(O@fp?v4`Z2&$nSxE3ABV7v%#!8|wxy?<`>QOrU9IrO=g(A}{s zNy0VSjdk%D8m*Z}LkFs%`D@S$+o28hMMpRovv4jtGrQ3aA4cmvg#|Is523!&Se1Nr z^y@e|kc2lpj8?n`-M=rQ6~2mY&pnvhcIZ@}L_3u2#}Lhha5VX$co*)$h1lh%(2+yv zCtBj?a1hnTYkmJOBGHxdZ$d$$`mwO4cc2X}MMt^@o$7sP2Y$dTJdggY*yMP)e;_)b zk!WyEMH)(MLt`t?iLk`2u#)?~Jqh>!M0A_og^u`9bS>A${1$9R{zJ^dJSW4^+8Mon zHX5{#qOtTc+L1S7`KM?Q9*^Z0pW-D(`-$Qt9NEoy2hPMg_#gTftn*8H;%&Sa-39GW zhakEUOOv01m*SJ?%&b9YZY#Q!A7Mk>hZ8W*ui+q?j>$3_p#t%i2ICE8GLbi@PW_3>!%&PLl=jCN!TIzxZ{&i=RJ|0r;(FZm;^ zO)<0sCDD=9K(9B(OzaRHi#EIfU4nC;#=v1CWZ%q7^&R?D7%Lkq6J!o(pMNiEC(3!dN@6fSI=nT|Bm$EfFGh@*ECZOK| zNpzsEqA#!HVG_Q*en7X&<^P2JIRM>uTd)fr!B$x3-|!(a1xJwIiH^A1x$tws-Izr_ zaX#1#ZzMYd*W*vv7MEN|nM@oa(TIYI|AjB0VK{*NV`#LW#p|#D{dH-^qbJx>Ol?Q> zKI9WXnkX_G<{b#vfpRteQO|F&O8f9Xy8BvB*Uksh3S#bn0hgIeZB(asMAAVb6|Q zfJH9ONFBvx&{(L9zBHPmyP!wR4@F~RG8#h*&^3Mn4c5=ldjE$8UH%*ysTit&UT==6 z|5ic=5(Yy*G>EQ48@w5<_-^#w@E{sgkD(o1j&84YXh$~2{BHFAz34VRi0-0e=t=u~ zynZ1^Ml$uf&7L!iEEi_uhN5W2CD4k>MXRD6s1t37zJ#*S-O(lHM@6S#S<3H42etvN ze;ZoQuAIp*H6Kx6L*Jt#JC2U%EIJdpa|KJIOHvPeNu-mgkG_1aL8qt(8Z3jbCfM@^|FM|=4IRkevAjU; zjMR5TF*FEkp)=bKxi6U*M8XkIMjM!kj&yD;Ul8+;VtLA!qUXZ9=m*Mo=t%xXgDp>< zP)|wpTD|-)O}-@&_-A zmX20O2hs@bXmf0i17rDeF3(8)RDC_xC%*`*;%=OYf5q}Ui?IJoQLu@GLG}e+jX8^kk!GP&+8GU| z!O>AzhWxGQj690Qz_aM?*$~U$h<<=2DgP2H<3H%GC|8XAZv_pDg$LV3d!lPJBzimg zz#_CmtI+3OL6>4D8kBp{SUMETkE2WY7k-5Ki)SRR!f(+5WL&}iH_9tq5gx3LMtj4U zZ-E{hozMmc<2N`Ced~3+GQ5OVVrTL{;4OG{iHy`sYz>Yee{so-)JyDUOzk3cK>L#< zoU)(s3j7mYiUOG#sn>5ybn3>U-+J?-8_?j|hnaW^{VvE`D!g2(qF=>5qPIjJh}U04 z>rEbr7yd#!aCzx)p$?X_0<@w#(Czmax&+%|`95@pena~Qq{NAXhk{~8iL@G2TC`!N$wp*Q9&7f!BBbgAm09cU5DZ$@7}v#|!QKo6w7 zSP_3jXC{C7Frf12ZfS>?(|)2W341mYyWrz!hfZKa%vB*H^;2srbbH>7ei6Na#zgUo zA^Mx39qxd3bRgQ1k?5|NhR*a{H25CHqz^n#!YO|Xjn4m}Yx;fk3>y78Dur0M9KG*q z^e37|(LR_-{#JDD7o!co6!UMQ_kW4b$eBtR;lGu1Y2~mD3!@ERi7rKTbWPi$`?f#Y z(ImP}m!SPE8Wpi@YMoW>rasPox(p@MJoSPEnq!;b19& zHc%OzvbJak2gK_)VF&VephxX{SPRptWu*S4L>=_GJJDTmKl=Q$=!|YjlJEd}9~;|;qg_w!U(dV~fJ3J8crR#*HYk&@*7rGrs;~-p& z1>OIDl5kCO)(ul%91W72&#1L~lU1 z=dE}#?I-RcVFh#0wOfX6lTB!YpQ6EY3{wr&3)hRHYgrdP8T+A2F%ccWLiG9P(R$xS z2l{pN6sG?DU!s1v@k(@iU5z%_3k|-T&|RcV|1y0Lf7~=bc+8)*SKthjMQIh ztA#G@i|BdqHad{q4cPxy_#FjC^}pzpr8Nu{7DgK=i9T2{S_iGLDOSOb*axSe_3gt< zJc+$9&oyDY-H6t6D`vywHSB+TK8pf(!F;sh<j~e}O&7A4Au$X`>JW*J4ld zgRn7fLI?CaI-`H1Gg!272;QsFfwoDKaEb?@Be@$L@k>}A-#|z9C%%rInuL$kTun0) z50IaY9wf!G;tZh!sDlPwbF{v0XoCaL`^Td*k-Uq9Bfk#~j-}`W8?X&-LEro7&BD}H z!Xe~aq7|%0-)1kOYrYvhir+;$@>9Hi23?v9SP_df&+yNflZoaeyl`jq;phf5YImbi zd?@DsMuRhNi(q+lw=_lX?~k4jG-z%> zD|#KBvJY`Aeuc(NyH?={?S@WmUvwZhp;JExpT$Se8EV!#yu{j}9U6V? zLT4s>r*L0Y%p~6d-QHu9By4Ck+Vk~jhh9UY`9t&oI*j)GJUa5CokMvIw4rO!>m$(J zGArhvMDO2>#?B{bhkr-6X|h052G{kSG;~j z*RWLe(532%F5$>nJ{gUn2atg#6RSvA@#avF_zYdspU}1Y2krUA-NHyKp~2J!r{WMa z+P}rxcvbfhoW0S8hN3Zc8@d#;(E+`Hjokk`NjReP9^uB~Xw+6gd)^SOs9nqtK%;*& z+QCWDS?Eh;5q86eWBzCKJUE4R-k$t90PQDAldzyHdP99Q7TTfPt2^4EX=p|B zFaw`JJH8z4(B_!mi7v_i(EHPSg`>Ja^h(U6yaFcO*X>ByU}tpA`l4$;9#bb7+JQ&V zrCE*6%xmZj?26@|p~3Y7T5tB=A<8d_w#1>7PeqsDyWZ@7D>zGmBgob#JeUvNpM}v5 zltgEyddz2`+pG&_;@xNrtw!(Liq^YV(})f1O8!LOjMT4QoAe9U=l5g(x8cHL6xe~o zxDijG9eBEbC|`%p%xma5@CiEgXVC4JcR=W96|^Ib(3$Fueu|Am@0*RzRZqkZ(}fHdWEzk5j-45!c|HP`Skf;s4nJc*8MRdfeBl}FIv%NP-Y zuPVANyJ3Bt9n0Ur^5l=A_2nKJmg=&R8DamIp}-N=jJ89gc?i0X??Hp+L3Axwqq}1Z zy5@(Xzn~+%fIfHWs4%diXou@Yd!qME9F+{)ZYBlp_lMAZ{UjO#ub^MKZ=zBD9y$|8 z(J9O}I?Pl)bl;anw{aF4GhNZ=hvNvGfClGR=zT|%Br-{yL8JWgF=3?T(U@q6sfy5` z8jg1GPV|UfjJ_itM@RH7x+I^WZ^H}N2y2ZEKmU)zR^*qV14tevQGvv-=n@pWDRih7 zI->4qPj5o+OQKWzELy=PbP2Yi1Na2pzK5_X78@5j)E0ey0y+ba<2?8ODiR-1Q0wN5 z#1D7@J>iay4>OQsLg+wAyovG}I02WTuiFB*gekrp-36tib+9S<);JvR!6kSKuQ7jX zMq;r0e;Wzc^s0$rB+bztc144(fAl7-MSeOO#T)Tzd;?vY-_e=Maa)+lqL_nxO*94? zqV={yW2-Z!{{3$+5^j&7Xq4WEKCl)?;VWoGr6z@`uY^`q4=Z9b^#0LkM{Yx7tcaV*YjXDBp#t-~as_Z#avNIDK;H;bmw=SDUj);2^9xC4=7=^JhzFJr(W@y*>p!kY->zd1HxTW}cy#|y#_qTP z-EN1`hJHmmcn;lu*`|dbU<#p2)*4;w?wC69(A_l?ow4L<5;piIR>M!xU`sGF>TgJW zumIX%CbDS~mGMbji!NEayF$ey(IuIOPW1z5Ogs_Yguc{1MxIM1PLOcwFP$DnToRqS za_GqFMVrR*HfV?Xpi@5#U6KjWH)8okGeUg@(2f_yvRDRfuRW&z{BIx$qj@|w!F$lP z--)jAw^$v2MH?(NGn|Z7u{`;<*bi?;m+Av_slLOWSa()NVmaQ2Rj|t4VQKneUZ-+c zEVvCF*-UiG7I_1%LDzf>I+FL%nfVM|y06jaPNVl7P?D23yU|Nd3>yyo3YDUw3bK;B{A0K@l%ck*LHmrtu7lwC8llzI@2|V!P{TYe7u*{7X(jC_-a!ynNc#0SVvNG{1p{U?#K zKN4Ohd$Bebay}ZiV`J<yP`h6Kz=HA!K$l6ebaCV`S-9cR(d8l7@L!S7?X2J93=4w4t+NKe0~n^AwT%J zjKn7V5eMOuYcf)QmGV5g1nt&_nR)`RCjT>b#LRW!FDl%Go`Cys308bQ?5?foN!w~Y z`+qQrS?l>L7;=&g)5|880I2`ZZ5FR`k9sgoR;!Vo`#Qym5OQAz~Uk<++y&f&! ziaqekjUhh)dy)Sbotd(ml3`!Z-V}a+|2z6%i&s)#r-@~FE%_?1hFvikuOq)5U5e7L zg_qT1cp3S%Xe?|+W9dEgHG2fzrvJozp3ULiQ8r1UEEhVWN9Zl+$@d~UrFpi5kJDCI zhWria&+&87AbcM4;w$KOdl!8d>_?;iNc1Ec^=Hu;$@O{&>f|62_G~n|R!Q`%-XCu$ z`bMa*EZSggY=(`{4o|~|_y*R;v*<3U@n+b5ZPB;u^>`OfLSOHHA={eY|GpJEP#Uf1 zYV@EPADxLdxELM57uXh$VF|4LcBrr$I^{#r-7yXS#P#S*tb8Yocs;s3w_twv|4tHa zhcD0rqS)5Z^O9Jce1mAuSUx^_A9^sYiut$D?RNm3sfydefm92-Zuq@IV?57& zv(TBCu`4XeJWM+B6(l@*zsH(b;=S+(3tg}!`RV9=@8bKI^Zl??`>;3-=GmQ*c!Tmv zAA}kF5e>={SQ^ivu~PEGutWo~9r?*0vi}>A*hWEZ{12U?+8>1(xE5=X?}v75KH8zT zu_R{uIIL+IoJPI|PQVw?87;jh+*cQ!xt@3l4n=2f+@55Zswos$@jP_uKSkI65c5 z4xN#Au?GGR?O@)$;oK;J?v|EV4tt>;pNc;BDEgj%5&aI>jLyvG=r&CLOu}gW2VH`U zec`05hX&saEQ61u2h}_1NPa+1x?j-|{)N_)?XwW|xzX~1Xn9F2j}_1XbV1Gq{{9~c zPqbUniXTBo@-&Xa-B=A9?hhjyiC2=p8-4C+G!}ND4gH8N!D-Bf=h2R&e;&+(wo?pK z|6A#0Nw{`(qMgw-9feNqWX!;O(S{eGQ@Rd~@{Qos$2O4Bk(T4Y;`}KQt1gFr3|3SZW(hr0Wk{sxoRzy402pvEN^tpa$ zM~0y@atnGSPsU^xiTNZl@niJCU!v#Hpt_*J~2!q=g^7P=(Yq74m1pBszD#9VZ{ zJ%f()ZFEUKLI-#VJqb^uOPKGQFmpwcBn+PFXaiZ;4ZEQ|UxmICHlh{3hn@?cquc2; z+E9`Ig^pE5J9G`&P+#=*Muo zhr)eDqE*nD$wFtc3wFnm=*+x{2Hk#ipvRDo^1qcx!nH2;U6_)x=#(`=%iEz9_CRN3 zEIMO%q7~0azXMjGBi(@p*8y|}zDMsn5%bv&hx;zYD!%_SV?jrBCWfFB|!#YfR?yC=FIQ-A;eD-w?2M+@+x@559Uz-8o1 zV55{x`G(=|6=f&5g!L8MNN|KPAHrEh+G5 z?2H~jJvY9rMZN zBz&+VT5&&g?QV+YPoh)02A%5dSP}Q4Q+^Ju_|g-h;mgsbDvQ>4b8+b4jB-Wu*_6FME$MO0%=!0j_4yT zvI9LAzCySAf0+9FKP66wf@{#J?}r{lbFnY3LucrsUqc5#n=RoAoV8`rO$wp&fh#?cg)$KwrYtzyEoggxll* zw!+`hw_<|}A!xGD85w{E(O!PoBUtsK&Jf{ezKa4cI0)md^e{4 z{C{68IAQ@8PM|?|4n2sn@qhL_C))Eu==o3zD`HEu;+xQpPDOXqeQ1!bMVD+VTHgo! z|JMHrrvCol0TMp=JwA-Tp$*PW3qFK4ybPUzb(kHuqx<}Qw1I!n`?95n4(3P8>!9^F zL6@dOEFYAfE&OjKj- zn^OJ`+R?;Cp@Wst2D_jGy8)en8R!5PCrLQ+4d|MFfdi92OVdYD*rTz@55!NJs8ye-$;{SE^7T{4OUE6g73GVI$*Wm8%?(XjH z(75XWgAeYm!QI{6eQ*W_81!Fv*Ut0i`|G+6tM;yKCFgW^5)$2po1kXC7plMukh9_S zT&1I>zYoj8H?Shi9of(K8%u^mE$u@%6ef@2=X-Oy1eQR)24}#8QT==`zZ;>p>N%VZ z1ETr)UZi$G-4*X(X&5cK@_6}@2^}r%5~v5w1(+GejNw+I7%Yc87V6MlhCSd{I2Cq` z=>qR&(i|7f?AQ2 zFgc7F*X?;um_y^RJe&=+-sII%A+Ol^L>J;Y#afVa2G59Uqi)7m(XoVTd0*<3w5|J8o$E)$jK79eDxD? z|EEJS3q=EX5XvA)VmGt8P%~cw1L0Aqt@#M+!xTx}iVT7+k=L6XE2%pRC81_M0BVcZ zoBp;jW-{)7B`lZBE$IlTneTwP;Tu>8rV4O-+#KdYUIlgg-G^F<=*itu7l+ahFs_9f zzY7&NS_)T4N!Sp%o0m=_I_IEfm?@!D@lXNwLGAf_sM{`8Dra94D)2C<#6eIi z^uosDrFQ=7ZayCg^=fM~5U$8W*q$)C?y=1wIUQD8E7_4oK@>bgCNrLlwRh zYKbpF+5LtpJWDz^k!Hr}#^Vq>ujd;b-NzZyyTjBOY9^~;Wq2EEB{F1i`bJPM)03d| zSD>Ex;WE0rs3g=1jD{*~JFE+zK-m|{+`+XQ7rlVitF1%0eC1$xuuF66(2-JS+6l=|@MeP8Xq;K0-D> z-*>}{z%IyZp>DIp+1-PuF1&_(26l!^a=7P2%$)9aYXJ4283y&5as+DT&!L_d33IuL zwSr!~`c0wJ3~q&0VBFk(zVGq2h8q6_wdC>h_<1(N#_%r~Bd?p`Mwk)#3rr7F=5zOd zC8*oCJJdU(>98+MnBUL$UA1ZXx&LdSxQ{~5{9Fb6e7_`O7*wU%3cA1p;Y8$>upP`@ z$j|q!mW5Dfa60F#w*d)xtPiMK+%irs^{O;eY3dt4vt#cCqd zU3DE^hl$F$34DaQP1BcmhtOM_j&7Ir@E5!bRoR^iZYdL2bdGDG5g8jJ{A*?3oz$3lPf)1l(bfl=WK7!PiTD&!n21@FN`FjYBzA; zOa{BaaBwn=31>nTuo^1xKB$?Vg$dzJs0Yd?W5rs|e<@VKYmGrLEb<{Jzmw3brFcRo z27C)uh^Mx_NrVcJ6zV-+dZ+?QK)ohZxAFcqKGL`VY63f<9z+MB3Jz1pJprRZ6;iqm z_rD6LghGMqKvmWnYOjZy;Vk01f%#*;x6mIG=_ z3e@HP*WOhy!)h=#avi7@=?D|U!B7r!p$b@IJP1|Db*MsK8vldx4`0t6wuDe}Dr0V_ z6)Eebqb08im8h8w3^tB~I?aDV6}kXw#g;)O-T+6zLof|2SKrOBE7aEYg-SdeY71vT zO=N?Od-v1PlkXH%MQ@=Je1lptzXong;=`oKIiTL#Rfh`L2P*MYD8C&r3Ooe0QWv1& zJcBCa8`SBK+|W01-v7~&K`tnVa!`SrKy5`osM}}~)JiOez8OFrqWw@Sc+AEx!z{>m zpbCrJ$bIz60Rxb$8N0*ey8oxq(ag3%ZNU}e2V>O6ZpIm562_}S*>#3Gtm93;-1JAF zCh`ylz%Nj*g7KR;`}|N3uv##t?*G1YRPlJIrP&Pw;B}L~LM?ghrf$#kLG5its6=(3 zmbMd=zZYr+=bC;Sl>Koi`%5qbd;z^$;#kexGd>;Ep=u0s!fsG!V>#6AcnWH%A3_Cq z3zaBAbLXD}YNqX=&cJx6@!3%RTc8R&1vA5E&AI=}(uvc;?QvtM3VTEC^#rK!2P$N1+10flBPx(%mI#pjIRkj1LP#tw0^9Gt(UEHKSciuN&x$LJ9goRXElR zml@YW1=s?0pYMaRKMSM7yT-Rrfq$Fq*UBBjI8ZBD5b7>#3ROr)uPJ&!EtzB`mo1lOg|9He=O9i>vX92TVZyF&#U1$}1&>eO$CD)0u>1L-ByO8R$n1;&Sxvzc5H zYNCyx@ArRpqNCF|8mhucuqvDjd&AeTDQwZn&-Z(Qk3iWK?(F<3K%J55Pz5!GDts8! zQvV5Mzu5E}pb9?RnfqT$cNK*ScmT`8=TI}u-o-tdD?*KTfYNt|TKWl4XJnbld!c4_ z4eGQ%gIckOUENs;fU++LOTwC6x&QS{pN*mo41xt=vTp9}bwj9vmKfhctxUe|&Tbsk zk{^fF;Unm~&3d>*^`P|Kj7MNC?p<;s zs1KK=pzMc3otX(x&xiR?d%YEEiH}3wB{!i8{TJ%RE_!eG0mhq!j`pSv)K>I_x_`Z} zB^(X4x38i0^qVniA19|U=7tJf-sE;rXJ-)9C!tR;Bh1s+#cctL>i$1LM-@lt=T;&y zRHBrygdB_mU|!_?Fc16+Gr+9<-DkrFa0K$-P!F2U1Kgoq1&bj^9OynPR)pHpK`_7W z{}XhEp$Id`y(*1@MUl6`dhoNc>R@*!R>K+SKfu&*G-pT!t%KR%QTni@I!vXY zUft?IotcI(D{KdKM&>}B_LWd)=sT2On6a*)I8c5?p~fqX<^IJL-`dL=iWILhuW$(P|uHvPz9_Q$6je*0}36& zt5AFU0%{2dk9QxdSHaB4cVGaFG{JpwDU-22OpksHRH2(;T6o3eA0{WA=n5|lHQvZe zM~Oy4-R~QrDn9`=)AJ_Zg1RdnLe2PJsDdI*ax>2lwFRZ2W>ynw1sg&o>In6mm~LDK zW$)c?og*+Mic3%dK0|HIFQ^AnxXJGAcq*uu*?drEp$ODgl!V%X22g%&OzsNzBM*eS zOA1bLcSBFuN%#L5Ivp6uKh=F{WI0TO9QhBo)H$G*wkAvm2g77=1=NZihh5=)SQS>C z=4QMI%I_r93V(!pQ=4?U+oBpUv+n<1baaZB!W{4_+yNuZa5Fyybw=*PQqccTKhHN< z0qTKNdZyb7FVx!HrhCFuM6-=XHYCCdjJ zG0+TZiTA?*cpqvhJ@Z}Q3{W#@2o-2BECkm<74Qn`OeFZrt!P%L*OHb{uMsC6G=Ms-)1c14Zm6w!1Pj1e3;lfm5uplHzzwhmjJ?R&je&ak-U)q=Zm5J+ z7rWPhE>JH*o1i9icd^$Uj&Mud(qxCZ7^n?3qY1D%JOy<&0++h8&3g<(zPPEdP! z2x=xbq3(w7CMRF(W>f;ozY}Z^r^8h6H`L)yway*lmQY)8267m^p09MKp-8;m&odlu zgnH4)xxp=EX{bZiA7+QEpic1}7yvyR-N)(VP%BpwYUayfEqEAeYZ7d7ua0t7Nvr^yUIg74|>1=xD0BgjzQng|D@aHmavSmJ^TZMiBRuk0(QH@S05He zUH}#F9@O0ueUD368U`SDghSv&*cV3K>+Z7AumtjEm;!#@%l)rTf_-jp3qrj}bb(5| z0_w0mggT_*_PZxtP8fh((d150XJRBw2vHFjulXc?v}~9 zj=QCA3$@bIpbqzTsIBzAqN7J^h7)ctTN~#=8C-&T`HXSW-9`nW?E6C%x*ZmSSK%@k z@06eKZ^rC}50Ph`c89S48F%QGz%%Im&-!Gq=MtUIC~}^21{u%0@7GO)^UyzodSnj2 z;NI7-gG&4Y>Qyb=MYp%LU;y%1sJmn}tPF2L`KP<&UVf`XjgR%o+-+p|&jl9qxY_45ZTl?u6rDoV$L$Kby4>c0_J{&z;`OP=_r3efKjV6`)T4 zRyY|xgZV@8<+}$?-t^EtA&Wh7FIvOkeC$ra(y;Ag?*FZHHavExz2y^kO1)5rZZXvT ze->)yAE644^^{M=B&rJaV$<%Ko6!}h6^Q%Xtz2`ct(yu*!SFBKZ9W<5Y{dWD>kdzw zzun=R3$rlr6Kd%LU%I7k2=zdk37f-dojK_!kU(?Oq+v zKt13-!XhxE_l^5Z)&c6-ALp&j2=+j}0-wOL?_6Pl@7-s?Mo=%ed!U{Vk6|?!`-5Al z)=+oHD5yhv1=fb4{&8Dc7xqE+9;KtDDfH2O_&fsjOmFhZJ<(>sYsgQb_IS^~&aUET zJGAgU`gUL3iq-$>=Q)Nv7HXzd|8q~yF>o02Mpzf-_~xhoTk7>pprc1`@$c?%orYbI zf5Dls^A8s|>`!-Sa>1tP2SPoH??OE(i~VvBnuf3r@<^yHxC?cNGyit)faXHohVl6r zlPMJUUjsT)j0xrMJKd+D?%Q853WqFDXvb1v{C$U}hM&KuDC3Kbk6}aPRQ~>+mT;)? z8PvoogmwBWP+OHRoWJke_U&OV@_TO5Q327y`}^KhR);$E%b;$z(@=-;7gS+^5&V6p zyfxH|%4XOXeu1T7*NFbUXMGTyihK)dB|1j(_ub}u;Uwe)k^T8A>2y}o(SzXw)S=57 z#ozZ=aF;^8jcy&)-}hnk0E~)!8m5G|pdb7NBfv1x{C%%}QJ`Ke3&8NO1=M&)7!me{ z{%~kCf3GJDozWuxJ*a?bWBB{N;#m}`@?CH_JPP%!?-hLXvIz#)Q>`ua1y8rLc(W!qAb?U<=a}Lp=5@mo|p)62KT^-7P0@R^hY}^Q=A@75V za}H|eH(*m3CcqWc63T86^s3T{bhOl4q3-XKP>1dYECItOceh_fsFi2|m7qOTVLhP^ z(@YoxZh$&72chCVgnDuN4E1~|pTghw^FaGjaR1LiQ8J}FeD|SlznH1~eP8)(0GlIU zgsoxD)c(E~kGU`-@+YY0K|mU}GTES>6CI$Id>Cv2=RrL|J!##{vp{W4k+fd-Os|Wg zFNzhgE(}QL9u)mxAo4J%rQQg2s`tTI@UHO#)J*-;yGMCKVT5iQVd zZF(quUX#5w>FANz0+xgw;bgc8Hi5-5yF;`H=0!dO{X;R+ENWJRj8IDhtEG`cSt~bEwnY z3#x#TFeRJ=eOm<;=PuMCeGavyzhO?@|AEbVI6kUv8mvUqvi;Y|g#V%ec?`%=*N^FJNwC_!(ir5_1% z!s9SB`~mfZ{0$qxOnLo%|0`|`)C1`!ls->Bf8SR=rop1fH(_U(Fu%X=ZTeW)75Nh^ z4qFxA{#R#Z0ry~d1$QAgDd?6eVIjw^umJjFPzgg9_V@i5T|t-}c`;PrBT%0Yausp! zC%VJx$g5xx_yg*_V!@*B9Z{X4UiYfD5=Bi0Ei(KrAs>QhdKj!OSzZvNnScxQ5=AJM1FvUV5!pXi8vhU z^lpcFWC!(zLnK;2$Nq3++RP=}}%)B~xljeDU^{bZ>3c5|TaiiJ=GuYxLYo9RzF^7)^R z?(5r7hHs$mhX0JA%DI_FfGQ+9)OY~Y4AR+ncBq->Gr6pdSAlwss0USWd#HRvedPX| zMn{Ke8B~JfPzkOZ??YAo9BRqGLnRJf-uXo~W;Rwec87YRPKP>MH=$N0a|OqoqVE6v zbhM;Jq3+ijP%F_ChJhWSmcA?01F08Oq0^vl(`)%MP)XdJC!A+=yFQ9Ilu$A0+a;U?U5o*u#K^0!o^wppWYyy?A3)ETY z1y$f+7#ohP#QZfd2Zc`S5~w}e19ccLL*68MUO}x$_{wf;VneM+VyFU=o16)zN6rS5 z!3I#Tb^~AloC&q}JD~EOs?7bbrN4|qOZ^OnhA*M+f;Uiwg{$K42?aATIEsuXVF8vZ zi=tR7hCU|KO~6vr)&Zi0RxsLkO`GP&_!I@A)j^lVi@_~A<#Cvd<0b~e5wtSF4>Oh< zhC=R+;|2>dovHJw+w)3mOZx}kX)I4D{Qt!MCVD@7Cy}T=JZ%Z}Vibf;Lo2#F^o5u( z^J1(}v1UGq70S=MjAcm9Komx9lJK%-NrGtT{$z%22oSP8BKRF_4q(3=--y15&LR$n zWS|v&L;FOlz_n2w9U$633RFAHIR81?>sf07)}ZLfpdT}9uf3tAR3Y?xlkhj=#TmcH zcqNidqQKecwxWx|>b$l%Jehs%6~1cEun%sJ>DNQwl1UY!KQRpV<7GNM2^1dXHv)Gj z;a#0J+Cp1O^>;IVhNR&M@Z5q%vhk``a8#S6>|>dY`p=laU2M;z+lK#K>=O_xNdF(d z7z1}l0=}t4*R|Y4$atH z3eHX3kJxgaJrjsC&L(ygyPXuF=aFX?%CHy~mtbuWPPYk?7-r=P;u=L_wS(BcAjx9_ zs%^J|yJ4d?oQs!y&v5+J_>a$?@r=ze`^V_t5Mxd#@_%4}|K96syQwMzNup8EBLXBx z4sJnorjS&vBIAXzi)wzAY*rdug-t~gR3yQ9D_|nA(y}@;EgoO_^)-Dx!qb$hp4tqg zeQdK-KQzU~(oDFD6YL-4#VmKo)&>1mu6@`fC24aCI8K6x80CU>;b_K7TJf)qit(?P zfg|iw$X1F1iet1GeLco&668+|-i9db6f+rzE-x$4&z!eX+(!ziK!K~7)FJ$bF@6*M zQd_AC#NZ2To^!;{0)6{Gl>oa)G#bY_sI4}z-2vjXN5xSlymf06{F2)di` zvs}EodfG9w3HWEFka6@&5OgqOanMy&EbJ3nVL2$|JB78trabn`=pWVn-=2XSB+9}S zhifo{-v}OofOF`pC8s}_0vqBy8pm=@?a}s~}jPdT+1-GWysZF7v>%=Vp_hPdJ z`@?28O80*|6wNS>!}XeK%W_2`(FewwSP&PA+0bcB!F*BEvk1EsR-ne0)9;5*Pl}%a z)zaeM0Q=z98Mz0=sGY^mTa7@Gxe5_10t4-FY(PO2>ltD}WE_ry9-F+EK|UOLULf~3 z-BD~Vp}R`5T+D906;=?I$DS`cdu|bb5B6b*H39j6{vM0ozlX)KDuY`sSQ@GK`*_LNY6};Gaip?BxC%2YEM=hOM*_V zJ{)5H`AsK%Iqd5xIjuC8_ZUX$a83@hSOM=SpgPsXVCH=YzJda`pi6~wQJdvyicmX= z-8gg+iP4W4CdRH6{qkZ`mYa+c`>Mg^AP&CsI~*fgE3Bq z{5JvA(&F%)LYgslk-pkJuE+GNFr$vxCAH!-ww>#|&2%P=iEj+@?4*C3{!7MV>;Aus zklgm>DZv{PFgr82XNlLbDxK)pN2eB(B;BkZm$B{m@5L_@W4(}Dl4Lhbfjok1BmKkZ z)LM{yJodqDke30zjpu7+8R!#YaD+Jxr?`BUFezIS+-|c{_ZT~ZT@8}7!uO&T-Hk+5 z(4U0SiNA|U-LZJ*nLr)%j|jy4Ik{ASz!Fa;XmC4&p;`ixWFc^Wfc%0ujeMirk(RU$e*e)w!Gs=hMPafP@L8ZQRurQX6~!f-_&q28Yeh*i zm|$^mj70F?6m>J)|8^}U!-a4?#_$cCY7ine{i|HTZ3p!Pw?yPUN*(VASp%E*unclc z`qj+d!iB^jG1tCq(?*^!@c%Q#**$Sc3i10&J_cL+5~LL)f#oLgewDFd>Qs`G)|@Fj{R6e~@H4I{tdS=dKmI z7~4f`MG5o+NxGJcAJOo&trT>XUNwtX0iRRYRVLmQIF#!(m)fp~?0;J;;4F@}&B+aO zYB5}czAVAdFpF;t zoBlSNwehBa zA#cP{?J71ixl-8#3fQcYQ*;UIC!(-p&fNF!d}4$lUSw>#X#f4`%&?`;!eD0{e=}pX z`Z)7DzC2kO|Bl^Z3eD|?{`c=|d@`H=EXMhTbiS4X-E=0C3b_lu{P>3F7je_txU*va zub^m(l$)wH6D${3a2sLEo5of^`tSrwg?xr<4gJ*E@FN+%_Jf3K{^oa-B6e89{{K&6 z&K$OFy(oWRT!qR;5O6QapV3dkRf}cb#?p?ZuXYf-3?ynw&@s$tIJW6+=FYDDW z68>sC9X!2kH5y>wh)Gn$CL+3%c5Y<$lfwTBP5zz;-$!3&RNn z`hv|p65gW7EQ}wcz^)`dK;S0$4#U0)_IxMA*N)>8(PBsu!B%B3wpWSyFMj#-XJ)cE zdrv_e-jP7<8pc;JNN@5k5-oN^o;{}Pi2X47DXztXW09?hqm0j> z_#aGAZHMf&{`;BPF$@oDh76uUcZGmUF;*)L!-ZJ728=Z#$s(>VW^)Q(KN3xMF1~-S zqq~K!JoX=m`;ZtBv5(EQ-r{-VVDJxvzb(*N`g19!DhyBs@!|4ceJY=<` z==e^Srw)aDC7D`llBqqwPHho#OKd(f+sbAWn;aL(cO??@?}gzk2C@)DZ3#%HX{w}$9SGqoDL?^%+ST7YJOtP^9|eL($RX8AhYXw%3F*a#EnjW zz7=%N#)X6UwL;&OSYCdMo#(C1^eD5tO(9>*xDxsijITi-nU!g1!Ix3U8JxG6-ClgQ z+U!i_>4p6v3R8Q)SS$)!Y1+V9#%};|Ij}Y7zwxp!n(dRYD#{zbVNNB1gnV zEi}5w7E|(JuLbRhLl-J8j)B^LuoZGO981_tq+dX=S|kd`m6t*TC}b%`q~zkqbv&gB zeuuG&_~k?w+>ToT%}^92_G$9Az~B29M*7jzT9tnWIV~#R%6&3 zxr5DoJ^DHpyeIZ;L(D91h$043WL(>tP9#?wN8&0rxu@9X#-E>Z^$Z~PQT>_m-5ABT z#P3KliY4Cfb9MhL4Kb?$6!wZFH()M?#}lYI#RMYfA>k~N-66p;OP+~-X0D1hk^A)h ztr$h`q5ThHaF5I7sbPhf=vhX^MQpZHDJ(9@{m^x#ua*tpY9v<+%b40H#+KWvG)BHj zj(d$kqX|MgZ;dka$6W-yun0am??<8iXLFqQ%KCO|?9cnX^|Bwb^EvMs`jRkL`N ziK9RE8r+`Y7Zdq8m+$%4%0{Z=umR-^_C$?KEDhV2)ug`cYu3XGSEw%-( zOHrHg`Ge$_80$n)Wf-dif7)uiG20SO_Ik#nC}OibL;nhng>5ExsPrtsu8^b+L4w<# z^!eEyPgu5PhxtCSVq}v8T?Z2Mhu0~%4{`O~>h;*yC0;q=M?;@5if{hGVI9XYi(w=_ zia{#PmLQAK)xxn6SAE9R{zG5WW+OR;lGrRCF{bz1*;$Rb6dFW7xXq@smUvguFVg4# zq!<^$IRnYm!m`|DNYaF0{Rq&2@g*d#0JjimH#5z}SZ#DgEus7-)Bh7)cVdQYAJM73 zv%;OkHe>I-Llv*+Snl94&qZ{86t&Tobs>pAVwBl}&8IMZcu^}%{~c_E+>PY?%3Y7w z>@E?b72|4E=FYnyaOi{a7c&Y)!u`lm?9ly<(^qWfQTQZ zv|P(JiE)Jn%LCBhZy%>;dvnOPT=tdc;xt37->V5(2#@j9NZ=73@Y$9@OTjuQ86}RzMu$ir3e1f;eW+V0k&Gx;m;!ei? zqNsn-&*mDd{ZC18GC5OEJat?R%&Nx=C7ahX=8&=EJ0NaK2bm%W3)4o>`!7GF~hJ^5!8 z#;;7Lfz?)|`U~g}w6TE%4{pQcOlxArsNanV-bD7|Q=B|C;aOsLXUsdFfsz&|jV+6u zGFtzPE#Wn6(sR%vFsp}L?eUvHuxc`tMOlLJk#-C=ld};6iG2kqQDqTpvpQvGoE+^8H#gZ0;_$+rV@eFQo*Fy zw`4|Dt@vMV$oKDS^tTv)f=>_l2ih#eNr!DIi?>;OqBg^3It;@i^v9qpPf-sjAQuJQ zAxHvdbrSmtTo=v$50cbmEVw12ABh-i8Bax`1!mXY^2qKiHr26F^LAzyrxBi$Ybs+YDE2cx zh43wj+)Q8p+kjyUt7Z}goiGk=H8I+S@pY&+lYYqdFNu1SBrcq3#c#u4Ird#CG7x3~$bhEYV3@wg;w{LtXkqDK4|=q7(QOS4oQBLg6Rr z_hl@V6>B=on5)xhE@&SNX!FZv_(&G^zd)}p#>A&h2`Xc+sH8(j~Aw#8|`?ejx| z4q?0&y36R=Bljgv8UkK}<*mT%MkP&WuclKd>O0peE0nv5?CQQ$Q6571`{o{iZZVj^|zT!bY?psnaSIK{R$6E+cDL3;kVRt%>B zBpzV=R#;}n zmoR~J=+%1piX{IblBl({Puuu@>@FA1pL+#HN~PZ0DOm8UV|qV&I^+lJ9wsMd-^7nwmv zb1Z1p?`3=t$rh7f5V~L3sSP4lYFpI;=-V>$T{adA+vLPK$Tob}kF}_6L|Mp!9`tE= zKS+>%1i3(hY&a|q0n~hH<#{RTzQP?jO@`!lV zEmtA_o2_Rq2Ak|q$fzYw->mvM^xslQI0F7)Y!`Ot39y9m^#plgvr0>hQ0UdBVe`up zv_O9u|HfRwEtak7af*qe{cn!rF9N>eT5eXO(Y;_yZ8px^C}diQnWP~}PR9OXmKDwJ zDRL1x(5eyK_oK()v--5j#9YO-hgfP~iQ(Nq&|a5u^FZ%PE!h;hR*`}b&1gho9gHW5^FvA70a{RW_D0PkYi#z zk7ABlA+l*r|272`#PKuoEc#)v--qv5g5Kfk#Y(;6N=Aa|*oRfYv?BBuq7OpfDn!g# z`u^WvIA)^aAp}Z5P_?WCj*hOs6Fv8sK{TfP0(k`i@?v)izY@sXkei#|F*whB8WLkZ zaxo^7lT$@;Gen;e3~X8A#ljD+4yq zvAv1@3rqac?4w|NiKHQ0f0OGmrsj=q6-kIpppY#yN&Z7OHben|*snIDd=%Y}0)C_W zL_ZnHSF^-{HeQpl@#yn&72{e+tOCT0X*p-;^}h`R^Kfd4b4L<|rm_MA$c{XcYCjXC zAWqepT}-av_Ag_Jkb@ZCfbBN?=z89vy8!#Jor&phMcJ7C0FsX&ms%L|%*DS#@cX}C zC}LoEnM6@wK@6*Koy2IGRr>%NKl-03R;{8HbCbm7DM0_zK&=wV?&7Cb8vBu|ik1ld zKkzd-Cel}n?fWw$blwwSCJC>$7zK z%wjA7*G4OFC2=z$Zy;uPSOq_|CiH{PpXW7-2`GjzyRKa4Ngm41!)X$v=8A{@19q<{ z;srAtXbBW79KOYoYhm}0ghP?PV7J~@;xC(oLi$?)bY1?k7$wA!pP}(2<2GA~T#jlF zajA_#UP@`$`LON4*C$kfNvaidrsm+y^fd23}MXLRW?G$`Yb46xCY7K0)hEvd9>^d+O zMPDZAL7t4+-`kMmNwEBT!Dp57Hk*!Wb6hsx9eez)t=nd_JOIL@|}={|h%_J1;W(-;YjkJ4b?*7zDS8$O&xuZeW**MAJx= z9-D3$-LRR}Pi0vB{>_|1`D&bEs;Q z?csY2D-$5T&0-REQyEXkim0_kzXRPTCwjtTtM*iS3cO43{n%b2b~`4~iz^(LT05@U z_~yaR8%VI*OxQ^GT%EAEV&bfnas8`G+8nNj3<%2jhNl4Mw4@P}vVQr{y-& zj@YQ>G$w|fi1SR(|D~qfPSQ>!m}W^6nDcUyHOF}aV`@#XD@d|<*zKj*%(exWu=_;N zDU3g1Yy}frZm|#Bgj@ukveCQCof)jz{+o!QW#19{qIsMQxS`NTxOc z-AXGsE(In-E=khg^bfK0*RYum*Wgb+&vEnLLF_ujNT;v=ZbE2)<3$FyW7U{}@aP^g z_J&I>0=gq4O2ffRM?W--fzNz5?3qM=G1mcBt+f?2nWPzz2caK;T|SCx$@NJ7cILeC z5!7axLj5}=uZ2D<1?5K{16^SXnMA+<0v1MI0q+v{1ypOu)z{bizkjk5XCZOIGN~yx z^G3w0_!I0Iop!*sRx+ zY#{wm%v7x|{ZZK8$8I@s-XK3DPHF63>-+y|PY7C>fNI(4kE1_^01t3HM==?3-UC-4 zU!i}Sv81*&QL!&Z;J6f_)`q}pFNrZ0o1G+liOmOFsjY6<{X6q+tWe?He=v(c{8wn_ zz|_QBPmb*L2N16UzP&8|ZsPsTcrnKH_(;w*lA2bler$S@Ya!#oZ87qGY=5FlMLe|` z*nP!EZHUu(J&mluj0CE0gEHvFq_<<#lR{dUO&KdDf-QpV{$#v4e$goC5;2ZoyMq2Y zF12LX_M!iZSPd!W6TIeb_MAdgvrr2V*>Gs)12VD(0w4;G30`ltUa+?E71S7nfGIb3ac+i1-G-* zGk`o{@X2TsS_n50t0WWtN}k!oX-usvL-<`LPYcEx*y<|YD&&3C5wZ^iW#<3~aBnCIaB=DQ9@DiIqDRgsffnpHYkA+hkXF>OvozwDSjDJpay(xYk6FX$Z zT}B?yVlCrZ8T{iY127(n@f-pl!T2M_^=*c4?P$zpybt4_(WkWJO4bMaRUuZRz7>?x zW?vQmnZZ@TG}wP9UsG64Z$~2H9EK%ah{Grh;!xoi`q8yz7;dGQnCK58f2W8C*j6TJ zHUdnv#4G9dMwf>1eb^?o*U(hrZgEJ~$@aYs{oC|&n*5QZ zRY{^20iOv3i^ohSqgU%dlDp{A(a(&21U8Q-;<&9|9>ybEVxI&5%wWv7|9|1oObIYb zC34l~nn%E=1Sw!e1ki75%i9p=zUbRwcavc6p%?r2Tro(V3^@RsQN(1ZKwg%h5wi0uY{W%NK(Y>V~A7;aD zCH{@@FF-=Ia@en+pd!dAu+Q(*o@H+SJRM0;52H(5A1(1S0#0R*{1{XFKp|?x_cW(} z-Y4-gVD=J=Ymio(l{!zXITVrr+aL7R&Jj;-1>;-s+pZ;hK!R=N7#71W7?h&go%9c2 zc*YX6^||tfj4^*^GRtIrq%DUoB`iVFYPX1)5svgpJk?p5xaewOr*_&V<4sKB!6bMN zH((HtOYI(pN6$6ynQlUcC+$cZs1%yJdw8bQGBICZ0_1jw1>!^(|8Kc0Z;kkxu3 zpT#$aE%Tq)1h<>msg1Mb>NnM2JwIv7TLI^N6fqpF%Ru0k7^I;8!uBZ=iG~oc9*O_M=_0aPBZ6nc?jm;0 z80*K_WiGXL*j#4(HP>#&z7sctC3}OMi=yUHgvTbeSszCZV5Bw!Wh_fxhW-t%qBt)m zSR>QR?vn!03gNegpif~r0w=;YDurxA?uA}05&F^S4-rEx3Na(1Q;WgmHV1$0<08si zRG*MU-EsJeL3rCU86PL;6>Ne?ev06KGWMB%YJBP<=Oyu6d}os=Bf80!WC5|>kmx*A zd(2f9-@C91m+$%ihCu&vEk&W0goAVrqkA@s$><&v_$M}h6RZcpHlu5bZVOi%^lFa?1RvUC*gD^k_r3k7Go<##@&jH*!-f%e|_T|1LOwScO}+)O$>Q06RM2; zPx@*J(f8!~#R~T#hT2=$4!djkMx*ARure_gQR4;1c4M!WM>oN91{+{B5r;Wi00Q?h zgZYf5$8a(Wkb}NjR{~5!-iyy41e}OI8~W$?jfN*>L_5Ty#wOl%TcqAr@B(~4G9HU- zT7-g}BLc)hc+NmEf?lPNn>hVR057vDLy+Khf$_^Eeda4LsCR-;X_AF*)-h;a-cZN= zf;trnmHK*6+&H15N1u7*Q>fTMiBgAt5;G`z-q6!?1;y(e`bv|T<<5nU5|r&+=%vwu z65R_uBYT>ffhD@PYSyz|m(GDDoAqp!KOlS7tT_X-W(&-gGay@@{5f-F%!+2_y69oz zN6MNnFe}=;`LpH-x)41~V8)>E1;Z5nHnU3}zlcF^a{Fb78NFQF?yUmq^=jU(LaTlO ndu diff --git a/lam/locale/de_DE/LC_MESSAGES/messages.po b/lam/locale/de_DE/LC_MESSAGES/messages.po index a7be1b8d..96271f1d 100644 --- a/lam/locale/de_DE/LC_MESSAGES/messages.po +++ b/lam/locale/de_DE/LC_MESSAGES/messages.po @@ -11,7 +11,7 @@ msgstr "" "Project-Id-Version: messages\n" "Report-Msgid-Bugs-To: post@rolandgruber.de \n" "POT-Creation-Date: 2004-01-14 17:45+0200\n" -"PO-Revision-Date: 2019-09-04 18:03+0100\n" +"PO-Revision-Date: 2019-12-07 12:48+0100\n" "Last-Translator: Roland Gruber \n" "Language-Team: German \n" "Language: de\n" @@ -38,9 +38,9 @@ msgstr "" #: ../lib/modules/zarafaUser.inc:162 ../lib/modules/zarafaUser.inc:165 #: ../lib/modules/zarafaUser.inc:168 ../lib/modules/zarafaUser.inc:1804 #: ../lib/modules/kopanoContact.inc:98 ../lib/modules/kopanoContact.inc:101 -#: ../lib/modules/kopanoContact.inc:104 ../lib/modules/kopanoContact.inc:810 +#: ../lib/modules/kopanoContact.inc:104 ../lib/modules/kopanoContact.inc:807 #: ../lib/modules/kopanoUser.inc:162 ../lib/modules/kopanoUser.inc:165 -#: ../lib/modules/kopanoUser.inc:168 ../lib/modules/kopanoUser.inc:1815 +#: ../lib/modules/kopanoUser.inc:168 ../lib/modules/kopanoUser.inc:1734 msgid "\"Send as\" attribute" msgstr "\"Senden als\"-Attribut" @@ -63,20 +63,20 @@ msgstr "\"Senden als\"-Attribut" #: ../lib/modules/zarafaUser.inc:1824 ../lib/modules/kopanoContact.inc:82 #: ../lib/modules/kopanoContact.inc:141 ../lib/modules/kopanoContact.inc:164 #: ../lib/modules/kopanoContact.inc:178 ../lib/modules/kopanoContact.inc:224 -#: ../lib/modules/kopanoContact.inc:337 ../lib/modules/kopanoContact.inc:590 +#: ../lib/modules/kopanoContact.inc:337 ../lib/modules/kopanoContact.inc:587 #: ../lib/modules/kopanoUser.inc:122 ../lib/modules/kopanoUser.inc:218 #: ../lib/modules/kopanoUser.inc:378 ../lib/modules/kopanoUser.inc:408 -#: ../lib/modules/kopanoUser.inc:442 ../lib/modules/kopanoUser.inc:488 -#: ../lib/modules/kopanoUser.inc:876 ../lib/modules/kopanoUser.inc:1275 -#: ../lib/modules/kopanoUser.inc:1500 ../lib/modules/kopanoUser.inc:1835 +#: ../lib/modules/kopanoUser.inc:442 ../lib/modules/kopanoUser.inc:472 +#: ../lib/modules/kopanoUser.inc:798 ../lib/modules/kopanoUser.inc:1194 +#: ../lib/modules/kopanoUser.inc:1419 ../lib/modules/kopanoUser.inc:1754 msgid "\"Send as\" privileges" msgstr "\"Senden als\"-Privilegien" -#: ../lib/modules/nisnetgroup.inc:137 +#: ../lib/modules/nisnetgroup.inc:136 msgid "(host1,user1,example.com);(host2,user2,example.com)" msgstr "(host1,benutzer1,firma.de);(host2,benutzer2,firma.de)" -#: ../lib/modules/qmailUser.inc:325 ../lib/modules/posixAccount.inc:234 +#: ../lib/modules/qmailUser.inc:325 ../lib/modules/posixAccount.inc:239 #: ../lib/modules/courierMailAccount.inc:476 msgid "/home/smiller" msgstr "/home/hmueller" @@ -106,16 +106,16 @@ msgstr "01234 123459" msgid "12345" msgstr "12345" -#: ../templates/config/confmain.php:459 -#: ../templates/selfService/adminMain.php:478 ../help/help.inc:298 +#: ../templates/config/confmain.php:463 +#: ../templates/selfService/adminMain.php:484 ../help/help.inc:300 msgid "2-factor authentication" msgstr "2-Faktor-Authentifizierung" -#: ../templates/selfService/adminMain.php:522 +#: ../templates/selfService/adminMain.php:534 msgid "2-factor authentication requires PHP curl extension." msgstr "Die 2-Faktor-Authentifizierung benötigt die PHP-Curl-Erweiterung." -#: ../lib/modules/locking389ds.inc:443 +#: ../lib/modules/locking389ds.inc:459 msgid "389ds" msgstr "389ds" @@ -185,7 +185,7 @@ msgid "A SOA record requires that the host name is set to \"@\"." msgstr "Ein SOA-Eintrag erfordert, dass der Hostname \"@\" ist." #: ../lib/modules/selfRegistration.inc:757 -#: ../lib/modules/passwordSelfReset.inc:1864 +#: ../lib/modules/passwordSelfReset.inc:1862 msgid "" "A confirmation mail was sent. Please click on the link in the mail to " "proceed." @@ -197,7 +197,7 @@ msgstr "" msgid "A description for this device." msgstr "Hier können Sie eine Beschreibung für das Gerät eintragen." -#: ../lib/modules/nsview.inc:77 +#: ../lib/modules/nsview.inc:76 msgid "A description for this view." msgstr "Hier können Sie eine Beschreibung für die Ansicht eintragen." @@ -262,8 +262,8 @@ msgstr "A/AAAA-Einträge" #: ../lib/modules/asteriskAccount.inc:269 #: ../lib/modules/asteriskAccount.inc:345 #: ../lib/modules/asteriskAccount.inc:608 -#: ../lib/modules/asteriskAccount.inc:986 -#: ../lib/modules/asteriskAccount.inc:1230 +#: ../lib/modules/asteriskAccount.inc:987 +#: ../lib/modules/asteriskAccount.inc:1231 msgid "AMA flags" msgstr "AMA-Flags" @@ -275,7 +275,7 @@ msgstr "Abbrechen" msgid "Aborted password change." msgstr "Passwortänderung wurde abgebrochen." -#: ../templates/config/confmain.php:215 ../help/help.inc:115 +#: ../templates/config/confmain.php:219 ../help/help.inc:115 msgid "Access level" msgstr "Zugriffsrechte" @@ -292,9 +292,9 @@ msgstr "Account" #: ../lib/modules/pykotaPrinter.inc:219 ../lib/modules/mitKerberos.inc:281 #: ../lib/modules/mitKerberos.inc:282 ../lib/modules/mitKerberos.inc:283 #: ../lib/modules/mitKerberos.inc:285 ../lib/modules/mitKerberos.inc:287 -#: ../lib/modules/mitKerberos.inc:288 ../lib/modules/kopanoServer.inc:151 -#: ../lib/modules/kopanoServer.inc:153 ../lib/modules/kopanoServer.inc:155 -#: ../lib/modules/kopanoServer.inc:156 ../lib/modules/autoDelete.inc:103 +#: ../lib/modules/mitKerberos.inc:288 ../lib/modules/kopanoServer.inc:150 +#: ../lib/modules/kopanoServer.inc:152 ../lib/modules/kopanoServer.inc:154 +#: ../lib/modules/kopanoServer.inc:155 ../lib/modules/autoDelete.inc:103 #: ../lib/modules/zarafaDynamicGroup.inc:181 #: ../lib/modules/zarafaDynamicGroup.inc:183 #: ../lib/modules/zarafaDynamicGroup.inc:185 @@ -322,9 +322,9 @@ msgstr "Account" #: ../lib/modules/qmailUser.inc:425 ../lib/modules/zarafaContact.inc:177 #: ../lib/modules/zarafaContact.inc:179 ../lib/modules/zarafaContact.inc:180 #: ../lib/modules/zarafaContact.inc:181 ../lib/modules/zarafaContact.inc:184 -#: ../lib/modules/zarafaContact.inc:185 ../lib/modules/windowsHost.inc:132 -#: ../lib/modules/posixGroup.inc:759 ../lib/modules/posixGroup.inc:763 -#: ../lib/modules/posixGroup.inc:764 ../lib/modules/zarafaGroup.inc:190 +#: ../lib/modules/zarafaContact.inc:185 ../lib/modules/windowsHost.inc:131 +#: ../lib/modules/posixGroup.inc:708 ../lib/modules/posixGroup.inc:712 +#: ../lib/modules/posixGroup.inc:713 ../lib/modules/zarafaGroup.inc:190 #: ../lib/modules/zarafaGroup.inc:192 ../lib/modules/zarafaGroup.inc:193 #: ../lib/modules/zarafaGroup.inc:194 ../lib/modules/zarafaGroup.inc:195 #: ../lib/modules/zarafaGroup.inc:197 ../lib/modules/device.inc:127 @@ -420,15 +420,15 @@ msgstr "Account" #: ../lib/modules/windowsUser.inc:1030 ../lib/modules/windowsUser.inc:1032 #: ../lib/modules/windowsUser.inc:1034 ../lib/modules/windowsUser.inc:1036 #: ../lib/modules/windowsUser.inc:1038 ../lib/modules/windowsUser.inc:1040 -#: ../lib/modules/windowsUser.inc:1041 ../lib/modules/windowsUser.inc:2417 -#: ../lib/modules/windowsUser.inc:2426 ../lib/modules/windowsUser.inc:2447 -#: ../lib/modules/windowsUser.inc:2458 ../lib/modules/windowsUser.inc:2515 +#: ../lib/modules/windowsUser.inc:1041 ../lib/modules/windowsUser.inc:2389 +#: ../lib/modules/windowsUser.inc:2398 ../lib/modules/windowsUser.inc:2419 +#: ../lib/modules/windowsUser.inc:2430 ../lib/modules/windowsUser.inc:2487 #: ../lib/modules/kolabUser.inc:213 ../lib/modules/kolabUser.inc:215 #: ../lib/modules/kolabUser.inc:216 ../lib/modules/kolabUser.inc:218 #: ../lib/modules/kolabUser.inc:220 ../lib/modules/kolabUser.inc:222 #: ../lib/modules/ddns.inc:151 ../lib/modules/nisObject.inc:124 -#: ../lib/modules/sambaGroupMapping.inc:577 -#: ../lib/modules/sambaGroupMapping.inc:578 ../lib/modules/pykotaUser.inc:275 +#: ../lib/modules/sambaGroupMapping.inc:574 +#: ../lib/modules/sambaGroupMapping.inc:575 ../lib/modules/pykotaUser.inc:275 #: ../lib/modules/pykotaUser.inc:277 ../lib/modules/pykotaUser.inc:279 #: ../lib/modules/pykotaUser.inc:281 ../lib/modules/pykotaUser.inc:283 #: ../lib/modules/pykotaUser.inc:284 ../lib/modules/pykotaUser.inc:286 @@ -442,10 +442,10 @@ msgstr "Account" #: ../lib/modules/kopanoDynamicGroup.inc:191 #: ../lib/modules/kopanoDynamicGroup.inc:192 #: ../lib/modules/kopanoDynamicGroup.inc:193 ../lib/modules/automount.inc:114 -#: ../lib/modules/yubiKeyUser.inc:130 ../lib/modules/sambaDomain.inc:209 -#: ../lib/modules/sambaDomain.inc:211 ../lib/modules/sambaDomain.inc:213 -#: ../lib/modules/sambaDomain.inc:215 ../lib/modules/sambaDomain.inc:217 -#: ../lib/modules/sambaDomain.inc:219 ../lib/modules/inetOrgPerson.inc:69 +#: ../lib/modules/yubiKeyUser.inc:134 ../lib/modules/sambaDomain.inc:208 +#: ../lib/modules/sambaDomain.inc:210 ../lib/modules/sambaDomain.inc:212 +#: ../lib/modules/sambaDomain.inc:214 ../lib/modules/sambaDomain.inc:216 +#: ../lib/modules/sambaDomain.inc:218 ../lib/modules/inetOrgPerson.inc:69 #: ../lib/modules/inetOrgPerson.inc:71 ../lib/modules/inetOrgPerson.inc:73 #: ../lib/modules/inetOrgPerson.inc:75 ../lib/modules/inetOrgPerson.inc:77 #: ../lib/modules/inetOrgPerson.inc:79 ../lib/modules/inetOrgPerson.inc:81 @@ -484,8 +484,8 @@ msgstr "Account" #: ../lib/modules/kopanoAddressList.inc:157 #: ../lib/modules/kopanoAddressList.inc:158 ../lib/modules/zarafaServer.inc:151 #: ../lib/modules/zarafaServer.inc:153 ../lib/modules/zarafaServer.inc:155 -#: ../lib/modules/zarafaServer.inc:156 ../lib/modules/nsview.inc:108 -#: ../lib/modules/nsview.inc:110 ../lib/modules/asteriskVoicemail.inc:211 +#: ../lib/modules/zarafaServer.inc:156 ../lib/modules/nsview.inc:107 +#: ../lib/modules/nsview.inc:109 ../lib/modules/asteriskVoicemail.inc:211 #: ../lib/modules/asteriskVoicemail.inc:215 #: ../lib/modules/asteriskVoicemail.inc:218 #: ../lib/modules/asteriskVoicemail.inc:220 @@ -514,8 +514,8 @@ msgstr "Account" #: ../lib/modules/heimdalKerberos.inc:248 #: ../lib/modules/heimdalKerberos.inc:250 #: ../lib/modules/heimdalKerberos.inc:252 -#: ../lib/modules/pykotaBillingCode.inc:128 -#: ../lib/modules/pykotaBillingCode.inc:130 ../lib/modules/windowsGroup.inc:280 +#: ../lib/modules/pykotaBillingCode.inc:127 +#: ../lib/modules/pykotaBillingCode.inc:129 ../lib/modules/windowsGroup.inc:280 #: ../lib/modules/windowsGroup.inc:282 ../lib/modules/windowsGroup.inc:283 #: ../lib/modules/windowsGroup.inc:284 ../lib/modules/windowsGroup.inc:286 #: ../lib/modules/nisMailAlias.inc:125 ../lib/modules/nisMailAlias.inc:127 @@ -530,13 +530,13 @@ msgstr "Account %s:" #: ../lib/modules/asteriskAccount.inc:265 #: ../lib/modules/asteriskAccount.inc:309 #: ../lib/modules/asteriskAccount.inc:591 -#: ../lib/modules/asteriskAccount.inc:982 +#: ../lib/modules/asteriskAccount.inc:983 #: ../lib/modules/asteriskExtension.inc:99 #: ../lib/modules/asteriskExtension.inc:130 #: ../lib/modules/asteriskExtension.inc:137 #: ../lib/modules/asteriskExtension.inc:153 #: ../lib/modules/asteriskExtension.inc:305 -#: ../lib/modules/asteriskExtension.inc:788 +#: ../lib/modules/asteriskExtension.inc:786 #: ../lib/modules/asteriskVoicemail.inc:90 #: ../lib/modules/asteriskVoicemail.inc:126 #: ../lib/modules/asteriskVoicemail.inc:136 @@ -558,7 +558,7 @@ msgstr "Bestätigung der Benutzeranlage" msgid "Account creation via file upload" msgstr "Erstellung von Accounts mittels Dateiupload" -#: ../lib/modules/posixAccount.inc:426 +#: ../lib/modules/posixAccount.inc:431 msgid "Account deactivated" msgstr "Account deaktiviert" @@ -566,8 +566,8 @@ msgstr "Account deaktiviert" msgid "Account details" msgstr "Accountdetails" -#: ../lib/types/user.inc:365 ../lib/types/user.inc:375 -#: ../lib/types/user.inc:1075 ../lib/types/user.inc:1112 +#: ../lib/types/user.inc:366 ../lib/types/user.inc:376 +#: ../lib/types/user.inc:1076 ../lib/types/user.inc:1113 #: ../lib/modules/windowsUser.inc:294 ../lib/modules/windowsUser.inc:771 #: ../lib/modules/windowsUser.inc:1033 msgid "Account expiration" @@ -575,22 +575,22 @@ msgstr "Kontoablauf" #: ../lib/types/user.inc:112 ../lib/modules/mitKerberos.inc:123 #: ../lib/modules/mitKerberos.inc:217 ../lib/modules/mitKerberos.inc:250 -#: ../lib/modules/mitKerberos.inc:376 ../lib/modules/mitKerberos.inc:626 -#: ../lib/modules/mitKerberos.inc:800 ../lib/modules/sambaSamAccount.inc:211 +#: ../lib/modules/mitKerberos.inc:377 ../lib/modules/mitKerberos.inc:631 +#: ../lib/modules/mitKerberos.inc:822 ../lib/modules/sambaSamAccount.inc:211 #: ../lib/modules/sambaSamAccount.inc:347 #: ../lib/modules/sambaSamAccount.inc:471 -#: ../lib/modules/sambaSamAccount.inc:1133 -#: ../lib/modules/sambaSamAccount.inc:1406 -#: ../lib/modules/sambaSamAccount.inc:1707 -#: ../lib/modules/sambaSamAccount.inc:1930 ../lib/modules/windowsUser.inc:286 +#: ../lib/modules/sambaSamAccount.inc:1169 +#: ../lib/modules/sambaSamAccount.inc:1450 +#: ../lib/modules/sambaSamAccount.inc:1754 +#: ../lib/modules/sambaSamAccount.inc:1977 ../lib/modules/windowsUser.inc:286 #: ../lib/modules/windowsUser.inc:290 ../lib/modules/windowsUser.inc:548 #: ../lib/modules/windowsUser.inc:868 ../lib/modules/windowsUser.inc:1227 -#: ../lib/modules/windowsUser.inc:1642 ../lib/modules/windowsUser.inc:2761 -#: ../lib/modules/windowsUser.inc:2947 ../lib/modules/shadowAccount.inc:129 +#: ../lib/modules/windowsUser.inc:1644 ../lib/modules/windowsUser.inc:2733 +#: ../lib/modules/windowsUser.inc:2919 ../lib/modules/shadowAccount.inc:129 #: ../lib/modules/shadowAccount.inc:171 ../lib/modules/shadowAccount.inc:194 -#: ../lib/modules/shadowAccount.inc:234 ../lib/modules/shadowAccount.inc:356 -#: ../lib/modules/shadowAccount.inc:466 ../lib/modules/shadowAccount.inc:571 -#: ../lib/modules/shadowAccount.inc:769 ../lib/modules/heimdalKerberos.inc:101 +#: ../lib/modules/shadowAccount.inc:234 ../lib/modules/shadowAccount.inc:358 +#: ../lib/modules/shadowAccount.inc:473 ../lib/modules/shadowAccount.inc:598 +#: ../lib/modules/shadowAccount.inc:796 ../lib/modules/heimdalKerberos.inc:101 #: ../lib/modules/heimdalKerberos.inc:186 #: ../lib/modules/heimdalKerberos.inc:218 #: ../lib/modules/heimdalKerberos.inc:313 @@ -610,15 +610,15 @@ msgstr "Account ist deaktiviert" #: ../lib/modules/sambaSamAccount.inc:287 #: ../lib/modules/sambaSamAccount.inc:293 #: ../lib/modules/sambaSamAccount.inc:463 -#: ../lib/modules/sambaSamAccount.inc:1098 -#: ../lib/modules/sambaSamAccount.inc:1703 ../lib/modules/windowsUser.inc:217 +#: ../lib/modules/sambaSamAccount.inc:1130 +#: ../lib/modules/sambaSamAccount.inc:1750 ../lib/modules/windowsUser.inc:217 #: ../lib/modules/windowsUser.inc:532 ../lib/modules/windowsUser.inc:858 -#: ../lib/modules/windowsUser.inc:1219 ../lib/modules/windowsUser.inc:2755 +#: ../lib/modules/windowsUser.inc:1219 ../lib/modules/windowsUser.inc:2727 msgid "Account is deactivated" msgstr "Account deaktiviert" #: ../lib/modules/sambaSamAccount.inc:290 -#: ../lib/modules/sambaSamAccount.inc:1104 +#: ../lib/modules/sambaSamAccount.inc:1136 msgid "Account is locked" msgstr "Account gesperrt" @@ -630,10 +630,10 @@ msgstr "Kontensperrung" msgid "Account name:" msgstr "Accountname:" -#: ../lib/types/user.inc:358 ../lib/types/user.inc:854 -#: ../lib/types/user.inc:1131 ../lib/modules/qmailUser.inc:177 +#: ../lib/types/user.inc:359 ../lib/types/user.inc:855 +#: ../lib/types/user.inc:1132 ../lib/modules/qmailUser.inc:177 #: ../lib/modules/qmailUser.inc:249 ../lib/modules/qmailUser.inc:359 -#: ../lib/modules/qmailUser.inc:574 ../lib/modules/qmailUser.inc:1108 +#: ../lib/modules/qmailUser.inc:477 ../lib/modules/qmailUser.inc:948 msgid "Account status" msgstr "Accountstatus" @@ -643,7 +643,7 @@ msgstr "Accountstatus" #: ../lib/modules/asteriskAccount.inc:267 #: ../lib/modules/asteriskAccount.inc:329 #: ../lib/modules/asteriskAccount.inc:593 -#: ../lib/modules/asteriskAccount.inc:984 ../lib/modules/customFields.inc:667 +#: ../lib/modules/asteriskAccount.inc:985 ../lib/modules/customFields.inc:667 #: ../lib/modules/customFields.inc:725 msgid "Account type" msgstr "Accounttyp" @@ -666,29 +666,29 @@ msgstr "" "Gruppen) verwaltet werden sollen. Die Account-Module legen fest, welche " "Attribute (z.B. Unix und Samba) bearbeitet werden können." -#: ../lib/modules.inc:1390 +#: ../lib/modules.inc:1396 msgid "Account was created successfully." msgstr "Account wurde erfolgreich erstellt." #: ../templates/lists/changePassword.php:782 #: ../templates/lists/changePassword.php:893 -#: ../templates/lists/changePassword.php:943 ../lib/modules.inc:1393 +#: ../templates/lists/changePassword.php:943 ../lib/modules.inc:1399 msgid "Account was modified successfully." msgstr "Account wurde erfolgreich geändert." -#: ../lib/passwordExpirationJob.inc:486 ../help/help.inc:406 +#: ../lib/passwordExpirationJob.inc:486 ../help/help.inc:412 msgid "Action" msgstr "Aktion" -#: ../lib/modules/customScripts.inc:155 +#: ../lib/modules/customScripts.inc:154 msgid "Action type" msgstr "Aktionsart" -#: ../lib/lists.inc:399 +#: ../lib/lists.inc:395 msgid "Actions" msgstr "Aktionen" -#: ../templates/lists/changePassword.php:415 ../lib/types/user.inc:507 +#: ../templates/lists/changePassword.php:415 ../lib/types/user.inc:508 msgid "Activate" msgstr "Aktivieren" @@ -696,8 +696,8 @@ msgstr "Aktivieren" msgid "Activate DynDNS" msgstr "DynDNS aktivieren" -#: ../templates/config/confmain.php:194 -#: ../templates/selfService/adminMain.php:422 +#: ../templates/config/confmain.php:198 +#: ../templates/selfService/adminMain.php:428 msgid "Activate TLS" msgstr "TLS aktivieren" @@ -741,7 +741,7 @@ msgstr "" "Wenn Sie dieses Kästchen aktivieren, dann der Client-Drucker als " "Standarddrucker verwendet." -#: ../help/help.inc:368 +#: ../help/help.inc:374 msgid "" "Activate this option to export internal attributes that are not visible by " "default." @@ -749,7 +749,7 @@ msgstr "" "Mit Aktivierung dieser Option werden auch interne Attribute exportiert, die " "sonst nicht sichtbar wären." -#: ../lib/modules/posixAccount.inc:361 +#: ../lib/modules/posixAccount.inc:366 msgid "Activating this checkbox will remove the user's home directory." msgstr "" "Wenn Sie dieses Kästchen aktivieren, dann wird das Heimatverzeichnis des " @@ -757,7 +757,7 @@ msgstr "" #: ../lib/types/kopanoDynamicGroupType.inc:82 #: ../lib/types/zarafaAddressListType.inc:95 -#: ../lib/types/zarafaDynamicGroupType.inc:96 ../lib/types/user.inc:337 +#: ../lib/types/zarafaDynamicGroupType.inc:96 ../lib/types/user.inc:338 #: ../lib/types/kopanoAddressListType.inc:81 #: ../lib/modules/zarafaDynamicGroup.inc:82 #: ../lib/modules/zarafaDynamicGroup.inc:143 @@ -784,8 +784,8 @@ msgstr "" #: ../lib/modules/kopanoDynamicGroup.inc:171 #: ../lib/modules/kopanoDynamicGroup.inc:226 #: ../lib/modules/kopanoDynamicGroup.inc:359 ../lib/modules/kopanoUser.inc:102 -#: ../lib/modules/kopanoUser.inc:277 ../lib/modules/kopanoUser.inc:624 -#: ../lib/modules/kopanoUser.inc:1839 ../lib/modules/kopanoAddressList.inc:70 +#: ../lib/modules/kopanoUser.inc:277 ../lib/modules/kopanoUser.inc:587 +#: ../lib/modules/kopanoUser.inc:1758 ../lib/modules/kopanoAddressList.inc:70 #: ../lib/modules/kopanoAddressList.inc:119 #: ../lib/modules/kopanoAddressList.inc:141 #: ../lib/modules/kopanoAddressList.inc:185 @@ -799,44 +799,41 @@ msgid "Active account types" msgstr "Aktive Accounttypen" #: ../templates/tools/serverInfo.php:279 ../templates/tools/serverInfo.php:351 -#: ../templates/tools/multiEdit.php:148 ../templates/config/profmanage.php:228 +#: ../templates/tools/multiEdit.php:150 ../templates/config/profmanage.php:228 #: ../templates/config/conftypes.php:169 #: ../templates/selfService/profManage.php:194 -#: ../templates/selfService/adminMain.php:684 -#: ../templates/selfService/adminMain.php:742 +#: ../templates/selfService/adminMain.php:696 +#: ../templates/selfService/adminMain.php:754 #: ../templates/pdfedit/pdfpage.php:402 ../templates/pdfedit/pdfpage.php:417 #: ../templates/pdfedit/pdfpage.php:429 ../templates/pdfedit/pdfpage.php:442 #: ../templates/3rdParty/pla/lib/TemplateRender.php:1093 #: ../templates/3rdParty/pla/lib/TemplateRender.php:1097 #: ../templates/3rdParty/pla/htdocs/add_attr_form.php:78 #: ../templates/3rdParty/pla/htdocs/add_attr_form.php:121 -#: ../lib/modules/pykotaPrinter.inc:268 ../lib/modules/pykotaPrinter.inc:429 -#: ../lib/modules/nisMailAliasUser.inc:162 -#: ../lib/modules/nisMailAliasUser.inc:199 -#: ../lib/modules/nisMailAliasUser.inc:393 ../lib/modules/zarafaContact.inc:422 +#: ../lib/modules/pykotaPrinter.inc:265 ../lib/modules/pykotaPrinter.inc:420 +#: ../lib/modules/nisMailAliasUser.inc:161 +#: ../lib/modules/nisMailAliasUser.inc:193 +#: ../lib/modules/nisMailAliasUser.inc:384 ../lib/modules/zarafaContact.inc:422 #: ../lib/modules/zarafaGroup.inc:436 ../lib/modules/device.inc:243 #: ../lib/modules/kopanoGroup.inc:388 ../lib/modules/nisNetGroupHost.inc:135 #: ../lib/modules/zarafaUser.inc:920 ../lib/modules/zarafaUser.inc:1496 -#: ../lib/modules/zarafaUser.inc:1527 ../lib/modules/kopanoContact.inc:375 +#: ../lib/modules/zarafaUser.inc:1527 ../lib/modules/kopanoContact.inc:374 #: ../lib/modules/bindDLZ.inc:813 ../lib/modules/bindDLZ.inc:908 #: ../lib/modules/bindDLZ.inc:995 ../lib/modules/bindDLZ.inc:1083 #: ../lib/modules/bindDLZ.inc:1197 ../lib/modules/bindDLZ.inc:1327 #: ../lib/modules/bindDLZ.inc:1493 ../lib/modules/bindDLZ.inc:1602 #: ../lib/modules/bindDLZ.inc:1719 ../lib/modules/kolabUser.inc:687 -#: ../lib/modules/kolabUser.inc:735 ../lib/modules/sambaGroupMapping.inc:367 -#: ../lib/modules/sambaGroupMapping.inc:466 ../lib/modules/pykotaUser.inc:341 -#: ../lib/modules/ldapPublicKey.inc:357 ../lib/modules/ldapPublicKey.inc:366 -#: ../lib/modules/nisNetGroupUser.inc:219 ../lib/modules/yubiKeyUser.inc:319 -#: ../lib/modules/yubiKeyUser.inc:328 ../lib/modules/nisnetgroup.inc:295 -#: ../lib/modules/inetOrgPerson.inc:1753 ../lib/modules/sudoRole.inc:320 -#: ../lib/modules/sudoRole.inc:347 ../lib/modules/sudoRole.inc:374 -#: ../lib/modules/sudoRole.inc:401 ../lib/modules/sudoRole.inc:428 -#: ../lib/modules/sudoRole.inc:455 ../lib/modules/kopanoUser.inc:920 -#: ../lib/modules/kopanoUser.inc:1497 ../lib/modules/kopanoUser.inc:1528 +#: ../lib/modules/kolabUser.inc:735 ../lib/modules/sambaGroupMapping.inc:372 +#: ../lib/modules/sambaGroupMapping.inc:465 ../lib/modules/pykotaUser.inc:373 +#: ../lib/modules/ldapPublicKey.inc:355 ../lib/modules/ldapPublicKey.inc:364 +#: ../lib/modules/nisNetGroupUser.inc:219 ../lib/modules/yubiKeyUser.inc:322 +#: ../lib/modules/yubiKeyUser.inc:323 ../lib/modules/nisnetgroup.inc:301 +#: ../lib/modules/inetOrgPerson.inc:1754 ../lib/modules/kopanoUser.inc:843 +#: ../lib/modules/kopanoUser.inc:1416 ../lib/modules/kopanoUser.inc:1447 #: ../lib/modules/customFields.inc:1243 ../lib/modules/customFields.inc:2569 -#: ../lib/modules/customFields.inc:2587 ../lib/modules/qmailGroup.inc:789 +#: ../lib/modules/customFields.inc:2587 ../lib/modules/qmailGroup.inc:642 #: ../lib/modules/organizationalRole.inc:304 ../lib/modules/fixed_ip.inc:608 -#: ../lib/modules/windowsGroup.inc:677 ../lib/modules/groupOfNames.inc:354 +#: ../lib/modules/windowsGroup.inc:656 ../lib/modules/groupOfNames.inc:354 #: ../lib/modules/groupOfNames.inc:503 msgid "Add" msgstr "Hinzufügen" @@ -869,7 +866,7 @@ msgstr "FreeRadius-Erweiterung hinzufügen" msgid "Add IP address extension" msgstr "IP-Erweiterung hinzufügen" -#: ../lib/modules/mitKerberos.inc:469 ../lib/modules/heimdalKerberos.inc:393 +#: ../lib/modules/mitKerberos.inc:474 ../lib/modules/heimdalKerberos.inc:393 msgid "Add Kerberos extension" msgstr "Kerberos-Erweiterung hinzufügen" @@ -882,7 +879,7 @@ msgstr "Kolab-Erweiterung hinzufügen" msgid "Add Kopano contact extension" msgstr "Kopano-Kontakt-Erweiterung hinzufügen" -#: ../lib/modules/kopanoServer.inc:214 ../lib/modules/kopanoGroup.inc:265 +#: ../lib/modules/kopanoServer.inc:194 ../lib/modules/kopanoGroup.inc:265 #: ../lib/modules/kopanoContact.inc:113 ../lib/modules/kopanoContact.inc:114 #: ../lib/modules/kopanoUser.inc:185 ../lib/modules/kopanoUser.inc:186 #: ../lib/modules/kopanoUser.inc:201 ../lib/modules/kopanoUser.inc:463 @@ -893,28 +890,28 @@ msgstr "Kopano-Erweiterung hinzufügen" msgid "Add Puppet extension" msgstr "Puppet-Erweiterung hinzufügen" -#: ../lib/modules/pykotaGroup.inc:217 ../lib/modules/pykotaUser.inc:389 +#: ../lib/modules/pykotaGroup.inc:216 ../lib/modules/pykotaUser.inc:389 msgid "Add PyKota extension" msgstr "PyKota-Erweiterung hinzufügen" -#: ../lib/modules/ldapPublicKey.inc:161 +#: ../lib/modules/ldapPublicKey.inc:159 msgid "Add SSH public key extension" msgstr "SSH-Schlüssel-Erweiterung hinzufügen" -#: ../lib/modules/sambaSamAccount.inc:1256 -#: ../lib/modules/sambaGroupMapping.inc:403 +#: ../lib/modules/sambaSamAccount.inc:1311 +#: ../lib/modules/sambaGroupMapping.inc:409 msgid "Add Samba 3 extension" msgstr "Samba 3-Erweiterung hinzufügen" -#: ../lib/modules/shadowAccount.inc:385 +#: ../lib/modules/shadowAccount.inc:386 msgid "Add Shadow account extension" msgstr "Shadow-Erweiterung hinzufügen" -#: ../lib/modules/posixGroup.inc:289 ../lib/modules/posixAccount.inc:1725 +#: ../lib/modules/posixGroup.inc:271 ../lib/modules/posixAccount.inc:1704 msgid "Add Unix extension" msgstr "Unix-Erweiterung hinzufügen" -#: ../lib/modules/yubiKeyUser.inc:159 +#: ../lib/modules/yubiKeyUser.inc:161 msgid "Add YubiKey extension" msgstr "YubiKey-Erweiterung hinzufügen" @@ -929,7 +926,7 @@ msgstr "Zarafa-Kontakt-Erweiterung hinzufügen" msgid "Add Zarafa extension" msgstr "Zarafa-Erweiterung hinzufügen" -#: ../lib/modules/inetOrgPerson.inc:794 ../lib/modules/inetOrgPerson.inc:3899 +#: ../lib/modules/inetOrgPerson.inc:794 ../lib/modules/inetOrgPerson.inc:3900 msgid "Add addressbook (ou=addressbook)" msgstr "Adressbuch hinzufügen (ou=addressbook)" @@ -950,7 +947,7 @@ msgstr "Neuen Attributwert hinzufügen" msgid "Add another rule" msgstr "Regel hinzufügen" -#: ../lib/modules/inetOrgPerson.inc:1787 +#: ../lib/modules/inetOrgPerson.inc:1788 ../lib/modules/qmailGroup.inc:683 #: ../lib/modules/organizationalRole.inc:309 #: ../lib/modules/groupOfNames.inc:406 ../lib/modules/groupOfNames.inc:555 msgid "Add entries" @@ -958,10 +955,10 @@ msgstr "Einträge hinzufügen" #: ../lib/modules/zarafaContact.inc:467 ../lib/modules/zarafaGroup.inc:482 #: ../lib/modules/device.inc:287 ../lib/modules/kopanoGroup.inc:433 -#: ../lib/modules/zarafaUser.inc:971 ../lib/modules/kopanoContact.inc:420 -#: ../lib/modules/sambaGroupMapping.inc:481 ../lib/modules/kopanoUser.inc:971 -#: ../lib/modules/qmailGroup.inc:832 ../lib/modules/organizationalRole.inc:321 -#: ../lib/modules/windowsGroup.inc:720 ../lib/modules/groupOfNames.inc:408 +#: ../lib/modules/zarafaUser.inc:971 ../lib/modules/kopanoContact.inc:418 +#: ../lib/modules/sambaGroupMapping.inc:478 ../lib/modules/kopanoUser.inc:893 +#: ../lib/modules/qmailGroup.inc:684 ../lib/modules/organizationalRole.inc:321 +#: ../lib/modules/windowsGroup.inc:700 ../lib/modules/groupOfNames.inc:408 #: ../lib/modules/groupOfNames.inc:565 msgid "Add entries of this type:" msgstr "Einträge diesen Typs hinzufügen:" @@ -982,7 +979,7 @@ msgstr "Feste IP-Adressen zum DNS hinzufügen" msgid "Add host extension" msgstr "Host-Erweiterung hinzufügen" -#: ../templates/selfService/adminMain.php:691 ../help/help.inc:286 +#: ../templates/selfService/adminMain.php:703 ../help/help.inc:288 msgid "Add input field" msgstr "Feld hinzufügen" @@ -994,7 +991,7 @@ msgstr "Job hinzufügen" msgid "Add mail routing extension" msgstr "Mail-Routing-Erweiterung hinzufügen" -#: ../templates/tools/multiEdit.php:172 +#: ../templates/tools/multiEdit.php:174 msgid "Add more fields" msgstr "Zusätzliche Felder hinzufügen" @@ -1028,7 +1025,7 @@ msgstr "Neues Binärattribut hinzufügen" msgid "Add new field" msgstr "Feld hinzufügen" -#: ../templates/selfService/adminMain.php:679 ../help/help.inc:284 +#: ../templates/selfService/adminMain.php:691 ../help/help.inc:286 msgid "Add new group" msgstr "Gruppe hinzufügen" @@ -1050,8 +1047,8 @@ msgid "Add password self reset extension" msgstr "Erweiterung für Passwortrücksetzung hinzufügen" #: ../lib/modules/windowsUser.inc:298 ../lib/modules/windowsUser.inc:398 -#: ../lib/modules/windowsUser.inc:1301 ../lib/modules/inetOrgPerson.inc:689 -#: ../lib/modules/inetOrgPerson.inc:1594 +#: ../lib/modules/windowsUser.inc:1304 ../lib/modules/inetOrgPerson.inc:689 +#: ../lib/modules/inetOrgPerson.inc:1595 msgid "Add photo" msgstr "Foto hinzufügen" @@ -1062,7 +1059,7 @@ msgstr "Foto hinzufügen" msgid "Add profile" msgstr "Neues Profil" -#: ../lib/modules/qmailUser.inc:677 ../lib/modules/qmailGroup.inc:598 +#: ../lib/modules/qmailUser.inc:547 ../lib/modules/qmailGroup.inc:495 msgid "Add qmail extension" msgstr "Qmail-Erweiterung hinzufügen" @@ -1070,7 +1067,7 @@ msgstr "Qmail-Erweiterung hinzufügen" msgid "Add selected" msgstr "Ausgewählte hinzufügen" -#: ../lib/modules/nisMailAliasUser.inc:380 +#: ../lib/modules/nisMailAliasUser.inc:371 msgid "Add to existing alias" msgstr "Zu bestehendem Alias hinzufügen" @@ -1078,7 +1075,7 @@ msgstr "Zu bestehendem Alias hinzufügen" msgid "Add value" msgstr "Wert hinzufügen" -#: ../lib/modules/posixGroup.inc:1054 ../lib/modules/posixGroup.inc:1097 +#: ../lib/modules/posixGroup.inc:1002 ../lib/modules/posixGroup.inc:1045 msgid "Added users" msgstr "Hinzugefügte Benutzer" @@ -1090,13 +1087,13 @@ msgstr "Füge hinzu" msgid "Adding the range failed because errors occured." msgstr "Hinzufügen des Adressbereiches schlug fehl." -#: ../templates/selfService/adminMain.php:585 ../help/help.inc:290 +#: ../templates/selfService/adminMain.php:597 ../help/help.inc:292 msgid "Additional CSS links" msgstr "Zusätzliche CSS-Links" #: ../templates/config/conftypes.php:245 -#: ../templates/selfService/adminMain.php:440 ../help/help.inc:182 -#: ../help/help.inc:294 +#: ../templates/selfService/adminMain.php:446 ../help/help.inc:182 +#: ../help/help.inc:296 msgid "Additional LDAP filter" msgstr "Zusätzlicher LDAP-Filter" @@ -1109,10 +1106,10 @@ msgstr "Zusätzlicher LDAP-Filter" msgid "Additional email addresses for this entry." msgstr "Zusätzliche EMail-Adressen für diesen Account." -#: ../lib/modules/posixAccount.inc:226 ../lib/modules/posixAccount.inc:299 -#: ../lib/modules/posixAccount.inc:348 ../lib/modules/posixAccount.inc:434 -#: ../lib/modules/posixAccount.inc:1658 ../lib/modules/posixAccount.inc:2011 -#: ../lib/modules/posixAccount.inc:2145 +#: ../lib/modules/posixAccount.inc:231 ../lib/modules/posixAccount.inc:304 +#: ../lib/modules/posixAccount.inc:353 ../lib/modules/posixAccount.inc:439 +#: ../lib/modules/posixAccount.inc:1641 ../lib/modules/posixAccount.inc:1977 +#: ../lib/modules/posixAccount.inc:2103 msgid "Additional groups" msgstr "Zusätzliche Gruppen" @@ -1128,7 +1125,7 @@ msgstr "Zusätzliche Optionen" msgid "Additional tasks for module:" msgstr "Zusätzliche Aufgaben des Moduls:" -#: ../lib/modules/windowsUser.inc:1132 ../lib/modules/inetOrgPerson.inc:1239 +#: ../lib/modules/windowsUser.inc:1131 ../lib/modules/inetOrgPerson.inc:1239 msgid "Address" msgstr "Adresse" @@ -1142,7 +1139,7 @@ msgstr "Anzahl Adresslisten: %s" msgid "Address of IMAP server (e.g. mail.example.org)." msgstr "Adresse des IMAP-Servers (z.B. mail.beispiel.de)." -#: ../help/help.inc:285 +#: ../help/help.inc:287 msgid "" "Adds a new group element to the list of self service options. Use this to " "structure the input fields." @@ -1154,7 +1151,7 @@ msgstr "" msgid "Adds a new range pool." msgstr "Fügt einen neuen Pool hinzu." -#: ../help/help.inc:287 +#: ../help/help.inc:289 msgid "Adds a new self service option to the selected group element." msgstr "Damit fügen Sie einer Gruppe ein neues Eingabefeld hinzu." @@ -1180,15 +1177,15 @@ msgstr "Fügt die Kolab-Erweiterung hinzu." #: ../lib/modules/zarafaUser.inc:393 ../lib/modules/zarafaUser.inc:634 #: ../lib/modules/zarafaUser.inc:1303 ../lib/modules/zarafaUser.inc:1829 #: ../lib/modules/kopanoUser.inc:126 ../lib/modules/kopanoUser.inc:287 -#: ../lib/modules/kopanoUser.inc:393 ../lib/modules/kopanoUser.inc:634 -#: ../lib/modules/kopanoUser.inc:1303 ../lib/modules/kopanoUser.inc:1840 +#: ../lib/modules/kopanoUser.inc:393 ../lib/modules/kopanoUser.inc:596 +#: ../lib/modules/kopanoUser.inc:1222 ../lib/modules/kopanoUser.inc:1759 msgid "Admin" msgstr "Administrator" #: ../lib/modules/selfRegistration.inc:60 #: ../lib/modules/selfRegistration.inc:191 #: ../lib/modules/passwordSelfReset.inc:107 -#: ../lib/modules/passwordSelfReset.inc:645 +#: ../lib/modules/passwordSelfReset.inc:643 msgid "Admin DN" msgstr "Administrator-DN" @@ -1201,7 +1198,7 @@ msgstr "Admin-email" #: ../lib/modules/selfRegistration.inc:64 #: ../lib/modules/selfRegistration.inc:195 ../lib/modules/imapAccess.inc:527 #: ../lib/modules/passwordSelfReset.inc:111 -#: ../lib/modules/passwordSelfReset.inc:648 +#: ../lib/modules/passwordSelfReset.inc:646 msgid "Admin password" msgstr "Adminstratorpasswort" @@ -1211,13 +1208,13 @@ msgstr "Adminstratorpasswort" msgid "Administration" msgstr "Verwaltung" -#: ../lib/modules/posixGroup.inc:78 ../lib/modules/pykotaGroup.inc:395 -#: ../lib/modules/nisnetgroup.inc:125 ../lib/modules/organizationalRole.inc:129 +#: ../lib/modules/posixGroup.inc:78 ../lib/modules/pykotaGroup.inc:394 +#: ../lib/modules/nisnetgroup.inc:124 ../lib/modules/organizationalRole.inc:129 #: ../lib/modules/groupOfNames.inc:140 msgid "Administrators group" msgstr "Administratoren" -#: ../templates/config/confmain.php:235 ../lib/modules/windowsUser.inc:3676 +#: ../templates/config/confmain.php:239 ../lib/modules/windowsUser.inc:3648 #: ../lib/modules/inetOrgPerson.inc:3932 msgid "Advanced options" msgstr "Erweiterte Optionen" @@ -1228,7 +1225,7 @@ msgstr "Erweiterte Optionen" msgid "Affiliations" msgstr "Zugehörigkeiten" -#: ../lib/modules/sambaDomain.inc:218 ../lib/modules/sambaDomain.inc:219 +#: ../lib/modules/sambaDomain.inc:217 ../lib/modules/sambaDomain.inc:218 msgid "Algorithmic RID base is not a number!" msgstr "RID-Basisnummer ist keine Zahl!" @@ -1256,7 +1253,7 @@ msgid "Alias is empty or invalid!" msgstr "Alias ist leer oder ungültig!" #: ../lib/types/mailAlias.inc:94 ../lib/modules/nisMailAliasUser.inc:96 -#: ../lib/modules/nisMailAliasUser.inc:370 ../lib/modules/bindDLZ.inc:140 +#: ../lib/modules/nisMailAliasUser.inc:362 ../lib/modules/bindDLZ.inc:140 #: ../lib/modules/bindDLZ.inc:144 ../lib/modules/bindDLZ.inc:290 #: ../lib/modules/bindDLZ.inc:308 ../lib/modules/bindDLZ.inc:494 #: ../lib/modules/bindDLZ.inc:497 ../lib/modules/bindDLZ.inc:584 @@ -1264,7 +1261,7 @@ msgstr "Alias ist leer oder ungültig!" #: ../lib/modules/bindDLZ.inc:1065 ../lib/modules/bindDLZ.inc:1872 #: ../lib/modules/bindDLZ.inc:1885 ../lib/modules/nisMailAlias.inc:80 #: ../lib/modules/nisMailAlias.inc:100 ../lib/modules/nisMailAlias.inc:114 -#: ../lib/modules/nisMailAlias.inc:162 ../lib/modules/nisMailAlias.inc:467 +#: ../lib/modules/nisMailAlias.inc:162 ../lib/modules/nisMailAlias.inc:470 msgid "Alias name" msgstr "Alias" @@ -1286,8 +1283,8 @@ msgstr "Der Aliasname ist ungültig." #: ../lib/modules/nisMailAliasUser.inc:100 #: ../lib/modules/nisMailAliasUser.inc:110 -#: ../lib/modules/nisMailAliasUser.inc:388 -#: ../lib/modules/nisMailAliasUser.inc:543 +#: ../lib/modules/nisMailAliasUser.inc:379 +#: ../lib/modules/nisMailAliasUser.inc:536 msgid "Alias names" msgstr "Aliasnamen" @@ -1306,7 +1303,7 @@ msgstr "Aliasnamen mit Benutzername" msgid "Alias timeout" msgstr "Zeitlimit für Aliase" -#: ../lib/types/alias.inc:95 ../lib/modules/customScripts.inc:52 +#: ../lib/types/alias.inc:95 ../lib/modules/customScripts.inc:51 #: ../lib/modules/uidObject.inc:58 ../lib/modules/aliasEntry.inc:61 #: ../lib/modules/aliasEntry.inc:69 ../lib/modules/aliasEntry.inc:77 #: ../lib/modules/aliasEntry.inc:98 ../lib/modules/aliasEntry.inc:111 @@ -1318,27 +1315,27 @@ msgstr "Aliasziel" msgid "Aliases" msgstr "Aliase" -#: ../lib/modules/nisMailAliasUser.inc:169 -#: ../lib/modules/nisMailAliasUser.inc:571 +#: ../lib/modules/nisMailAliasUser.inc:166 +#: ../lib/modules/nisMailAliasUser.inc:564 msgid "Aliases for email" msgstr "Aliase für Email" #: ../lib/modules/nisMailAliasUser.inc:139 -#: ../lib/modules/nisMailAliasUser.inc:570 +#: ../lib/modules/nisMailAliasUser.inc:563 msgid "Aliases for user name" msgstr "Aliase für Benutzername" -#: ../lib/lists.inc:809 +#: ../lib/lists.inc:798 #, php-format msgid "All accounts (%s)" msgstr "Alle Accounts (%s)" -#: ../templates/selfService/selfServiceMain.php:414 +#: ../templates/selfService/selfServiceMain.php:415 #: ../templates/initsuff.php:169 msgid "All changes were successful." msgstr "Alle Änderungen waren erfolgreich." -#: ../lib/lists.inc:808 +#: ../lib/lists.inc:797 #, php-format msgid "All selected accounts (%s)" msgstr "Alle selektierten Accounts (%s)" @@ -1347,18 +1344,18 @@ msgstr "Alle selektierten Accounts (%s)" msgid "Allow" msgstr "Erlauben" -#: ../templates/config/confmain.php:372 ../help/help.inc:341 +#: ../templates/config/confmain.php:376 ../help/help.inc:347 msgid "Allow alternate address" msgstr "Alternative Adresse erlauben" #: ../lib/modules/passwordSelfReset.inc:167 #: ../lib/modules/passwordSelfReset.inc:298 -#: ../lib/modules/passwordSelfReset.inc:653 +#: ../lib/modules/passwordSelfReset.inc:651 msgid "Allow custom security questions" msgstr "Eigene Sicherheitsfragen erlauben" -#: ../lib/modules/sambaDomain.inc:117 ../lib/modules/sambaDomain.inc:194 -#: ../lib/modules/sambaDomain.inc:308 ../lib/modules/sambaDomain.inc:620 +#: ../lib/modules/sambaDomain.inc:116 ../lib/modules/sambaDomain.inc:193 +#: ../lib/modules/sambaDomain.inc:301 ../lib/modules/sambaDomain.inc:617 msgid "Allow machine password changes" msgstr "PC-Passwortänderungen erlauben" @@ -1370,24 +1367,24 @@ msgstr "PC-Passwortänderungen erlauben" msgid "Allow multiple values" msgstr "Mehrere Werte erlauben" -#: ../lib/types/ppolicyType.inc:93 ../lib/modules/ppolicy.inc:132 -#: ../lib/modules/ppolicy.inc:155 ../lib/modules/ppolicy.inc:289 -#: ../lib/modules/ppolicy.inc:504 +#: ../lib/types/ppolicyType.inc:93 ../lib/modules/ppolicy.inc:131 +#: ../lib/modules/ppolicy.inc:154 ../lib/modules/ppolicy.inc:282 +#: ../lib/modules/ppolicy.inc:497 msgid "Allow password change" msgstr "Passwortänderung erlauben" -#: ../templates/config/confmain.php:325 ../help/help.inc:218 +#: ../templates/config/confmain.php:329 ../help/help.inc:218 msgid "Allow setting specific passwords" msgstr "Erlaube Setzen eines spezifischen Passwortes" #: ../lib/modules/sambaSamAccount.inc:231 #: ../lib/modules/sambaSamAccount.inc:353 -#: ../lib/modules/sambaSamAccount.inc:1457 -#: ../lib/modules/sambaSamAccount.inc:1939 +#: ../lib/modules/sambaSamAccount.inc:1516 +#: ../lib/modules/sambaSamAccount.inc:1986 msgid "Allow terminal server login" msgstr "Terminalserver-Login erlauben" -#: ../templates/config/confmain.php:331 ../help/help.inc:220 +#: ../templates/config/confmain.php:335 ../help/help.inc:220 msgid "Allow to display password on screen" msgstr "Erlaube Passwortanzeige auf dem Bildschirm" @@ -1395,7 +1392,7 @@ msgstr "Erlaube Passwortanzeige auf dem Bildschirm" msgid "Allow to enter custom security questions." msgstr "Erlaube die Eingabe von eigenen Sicherheitsfragen." -#: ../templates/selfService/adminMain.php:644 +#: ../templates/selfService/adminMain.php:656 msgid "Allow user input for this field." msgstr "Benutzereingaben für dieses Feld erlauben." @@ -1403,8 +1400,8 @@ msgstr "Benutzereingaben für dieses Feld erlauben." #: ../lib/modules/asteriskAccount.inc:287 #: ../lib/modules/asteriskAccount.inc:477 #: ../lib/modules/asteriskAccount.inc:711 -#: ../lib/modules/asteriskAccount.inc:1004 -#: ../lib/modules/asteriskAccount.inc:1248 +#: ../lib/modules/asteriskAccount.inc:1005 +#: ../lib/modules/asteriskAccount.inc:1249 msgid "Allowed codec" msgstr "Erlaubter Codec" @@ -1451,8 +1448,8 @@ msgstr "Erlaubte Empfänger" msgid "Allowed senders" msgstr "Erlaubte Absender" -#: ../lib/modules/sambaSamAccount.inc:1301 -#: ../lib/modules/sambaSamAccount.inc:1302 +#: ../lib/modules/sambaSamAccount.inc:1358 +#: ../lib/modules/sambaSamAccount.inc:1368 msgid "Allowed workstations" msgstr "Erlaubte PCs" @@ -1476,15 +1473,15 @@ msgstr "Erlaubt das Setzen eines spezifischen Passwortes über ein Eingabefeld." #: ../templates/lists/changePassword.php:334 #: ../templates/lists/changePassword.php:370 ../lib/modules/qmailUser.inc:169 #: ../lib/modules/qmailUser.inc:237 ../lib/modules/qmailUser.inc:357 -#: ../lib/modules/qmailUser.inc:404 ../lib/modules/qmailUser.inc:485 -#: ../lib/modules/qmailUser.inc:1091 ../lib/modules/qmailGroup.inc:96 +#: ../lib/modules/qmailUser.inc:404 ../lib/modules/qmailUser.inc:451 +#: ../lib/modules/qmailUser.inc:931 ../lib/modules/qmailGroup.inc:96 #: ../lib/modules/qmailGroup.inc:100 ../lib/modules/qmailGroup.inc:212 #: ../lib/modules/qmailGroup.inc:335 ../lib/modules/qmailGroup.inc:387 -#: ../lib/modules/qmailGroup.inc:449 ../lib/modules/qmailGroup.inc:974 +#: ../lib/modules/qmailGroup.inc:416 ../lib/modules/qmailGroup.inc:825 msgid "Alternate address" msgstr "Alternative Adresse" -#: ../lib/modules.inc:1156 ../lib/modules.inc:1255 ../help/help.inc:266 +#: ../lib/modules.inc:1157 ../lib/modules.inc:1256 ../help/help.inc:268 msgid "Alternate recipient" msgstr "Alternativer Empfänger" @@ -1496,8 +1493,8 @@ msgstr "Immer akzeptieren" msgid "Always reject" msgstr "Immer ablehnen" -#: ../lib/modules/pykotaUser.inc:336 ../lib/modules/pykotaUser.inc:562 -#: ../lib/modules/pykotaUser.inc:923 ../lib/modules/pykotaUser.inc:987 +#: ../lib/modules/pykotaUser.inc:369 ../lib/modules/pykotaUser.inc:561 +#: ../lib/modules/pykotaUser.inc:905 ../lib/modules/pykotaUser.inc:969 msgid "Amount" msgstr "Betrag" @@ -1525,13 +1522,13 @@ msgstr "" #: ../lib/modules/passwordSelfReset.inc:322 #: ../lib/modules/passwordSelfReset.inc:326 #: ../lib/modules/passwordSelfReset.inc:329 -#: ../lib/modules/passwordSelfReset.inc:498 -#: ../lib/modules/passwordSelfReset.inc:501 -#: ../lib/modules/passwordSelfReset.inc:504 +#: ../lib/modules/passwordSelfReset.inc:496 +#: ../lib/modules/passwordSelfReset.inc:499 +#: ../lib/modules/passwordSelfReset.inc:502 +#: ../lib/modules/passwordSelfReset.inc:797 +#: ../lib/modules/passwordSelfReset.inc:798 #: ../lib/modules/passwordSelfReset.inc:799 -#: ../lib/modules/passwordSelfReset.inc:800 -#: ../lib/modules/passwordSelfReset.inc:801 -#: ../lib/modules/passwordSelfReset.inc:1281 +#: ../lib/modules/passwordSelfReset.inc:1279 msgid "Answer" msgstr "Antwort" @@ -1555,18 +1552,18 @@ msgstr "Aussehen" #: ../lib/modules/asteriskExtension.inc:111 #: ../lib/modules/asteriskExtension.inc:166 #: ../lib/modules/asteriskExtension.inc:366 -#: ../lib/modules/asteriskExtension.inc:796 +#: ../lib/modules/asteriskExtension.inc:794 msgid "Application" msgstr "Anwendung" #: ../lib/modules/asteriskExtension.inc:95 #: ../lib/modules/asteriskExtension.inc:173 #: ../lib/modules/asteriskExtension.inc:374 -#: ../lib/modules/asteriskExtension.inc:797 +#: ../lib/modules/asteriskExtension.inc:795 msgid "Application data" msgstr "Anwendungsdaten" -#: ../templates/tools/multiEdit.php:190 +#: ../templates/tools/multiEdit.php:192 msgid "Apply changes" msgstr "Änderungen speichern" @@ -1576,13 +1573,13 @@ msgstr "Änderungen speichern" #: ../lib/modules/zarafaUser.inc:1277 ../lib/modules/zarafaUser.inc:1359 #: ../lib/modules/zarafaUser.inc:1830 ../lib/modules/kopanoUser.inc:177 #: ../lib/modules/kopanoUser.inc:181 ../lib/modules/kopanoUser.inc:339 -#: ../lib/modules/kopanoUser.inc:405 ../lib/modules/kopanoUser.inc:584 -#: ../lib/modules/kopanoUser.inc:1015 ../lib/modules/kopanoUser.inc:1277 -#: ../lib/modules/kopanoUser.inc:1359 ../lib/modules/kopanoUser.inc:1841 +#: ../lib/modules/kopanoUser.inc:405 ../lib/modules/kopanoUser.inc:548 +#: ../lib/modules/kopanoUser.inc:936 ../lib/modules/kopanoUser.inc:1196 +#: ../lib/modules/kopanoUser.inc:1278 ../lib/modules/kopanoUser.inc:1760 msgid "Archive servers" msgstr "Archivserver" -#: ../lib/modules/zarafaUser.inc:582 ../lib/modules/kopanoUser.inc:582 +#: ../lib/modules/zarafaUser.inc:582 ../lib/modules/kopanoUser.inc:546 msgid "Archiving" msgstr "Archivierung" @@ -1635,7 +1632,7 @@ msgstr "Asterisk-Erweiterungseinträge" #: ../lib/modules/asteriskAccount.inc:122 #: ../lib/modules/asteriskAccount.inc:248 -#: ../lib/modules/asteriskAccount.inc:1223 +#: ../lib/modules/asteriskAccount.inc:1224 msgid "Asterisk realm" msgstr "Asterisk-Bereich" @@ -1665,8 +1662,8 @@ msgstr "Attribut" msgid "Attribute doesn't exist" msgstr "Attribut existiert nicht" -#: ../templates/tools/multiEdit.php:142 ../lib/modules/yubiKeyUser.inc:100 -#: ../lib/modules/yubiKeyUser.inc:575 ../lib/modules/yubiKeyUser.inc:586 +#: ../templates/tools/multiEdit.php:144 ../lib/modules/yubiKeyUser.inc:100 +#: ../lib/modules/yubiKeyUser.inc:572 ../lib/modules/yubiKeyUser.inc:583 #: ../lib/modules/customFields.inc:97 ../lib/modules/customFields.inc:201 #: ../lib/modules/customFields.inc:1220 ../lib/modules/customFields.inc:1968 #: ../lib/modules/customFields.inc:4098 @@ -1687,7 +1684,7 @@ msgstr "Attributtypen" #: ../templates/tools/importexport.php:279 #: ../lib/modules/selfRegistration.inc:77 -#: ../lib/modules/selfRegistration.inc:207 ../help/help.inc:365 +#: ../lib/modules/selfRegistration.inc:207 ../help/help.inc:371 msgid "Attributes" msgstr "Attribute" @@ -1738,28 +1735,28 @@ msgstr "Autovervollständigung" msgid "Automatic PTR changes" msgstr "Automatische PTR Änderungen" -#: ../lib/modules/customScripts.inc:154 -msgid "Automatical scripts" +#: ../lib/modules/customScripts.inc:153 +msgid "Automatic scripts" msgstr "Automatische Skripte" #: ../lib/modules/kolabGroup.inc:108 ../lib/modules/kolabGroup.inc:118 #: ../lib/modules/puppetClient.inc:115 ../lib/modules/puppetClient.inc:172 #: ../lib/modules/qmailUser.inc:98 ../lib/modules/qmailUser.inc:229 #: ../lib/modules/zarafaContact.inc:90 ../lib/modules/zarafaContact.inc:651 -#: ../lib/modules/posixGroup.inc:444 ../lib/modules/posixGroup.inc:531 +#: ../lib/modules/posixGroup.inc:393 ../lib/modules/posixGroup.inc:480 #: ../lib/modules/eduPerson.inc:149 ../lib/modules/eduPerson.inc:154 #: ../lib/modules/pykotaGroup.inc:123 ../lib/modules/pykotaGroup.inc:134 #: ../lib/modules/sambaSamAccount.inc:165 -#: ../lib/modules/sambaSamAccount.inc:404 ../lib/modules/posixAccount.inc:310 -#: ../lib/modules/posixAccount.inc:2051 ../lib/modules/zarafaUser.inc:154 +#: ../lib/modules/sambaSamAccount.inc:404 ../lib/modules/posixAccount.inc:315 +#: ../lib/modules/posixAccount.inc:2014 ../lib/modules/zarafaUser.inc:154 #: ../lib/modules/zarafaUser.inc:1345 ../lib/modules/kopanoContact.inc:90 -#: ../lib/modules/kopanoContact.inc:606 +#: ../lib/modules/kopanoContact.inc:603 #: ../lib/modules/courierMailAccount.inc:132 #: ../lib/modules/courierMailAccount.inc:143 ../lib/modules/pykotaUser.inc:163 #: ../lib/modules/pykotaUser.inc:170 ../lib/modules/shadowAccount.inc:111 #: ../lib/modules/shadowAccount.inc:198 ../lib/modules/ldapPublicKey.inc:97 #: ../lib/modules/ldapPublicKey.inc:103 ../lib/modules/kopanoUser.inc:154 -#: ../lib/modules/kopanoUser.inc:1345 +#: ../lib/modules/kopanoUser.inc:1264 #: ../lib/modules/inetLocalMailRecipient.inc:95 #: ../lib/modules/customFields.inc:1022 ../lib/modules/qmailGroup.inc:67 #: ../lib/modules/qmailGroup.inc:197 @@ -1808,22 +1805,22 @@ msgid "Autoreply" msgstr "Automatische Antwort" #: ../lib/modules/qmailUser.inc:205 ../lib/modules/qmailUser.inc:307 -#: ../lib/modules/qmailUser.inc:378 ../lib/modules/qmailUser.inc:604 -#: ../lib/modules/qmailUser.inc:1098 ../lib/modules/qmailUser.inc:1220 +#: ../lib/modules/qmailUser.inc:378 ../lib/modules/qmailUser.inc:500 +#: ../lib/modules/qmailUser.inc:938 ../lib/modules/qmailUser.inc:1060 msgid "Autoreply text" msgstr "Text für automatische Antwort" -#: ../templates/config/conftypes.php:159 ../lib/modules/customScripts.inc:61 +#: ../templates/config/conftypes.php:159 ../lib/modules/customScripts.inc:60 msgid "Available account types" msgstr "Verfügbare Accounttypen" -#: ../lib/modules/customScripts.inc:62 ../lib/modules/customScripts.inc:75 +#: ../lib/modules/customScripts.inc:61 ../lib/modules/customScripts.inc:74 msgid "Available actions" msgstr "Verfügbare Aktionen" -#: ../lib/modules/posixAccount.inc:1805 ../lib/modules/posixAccount.inc:1836 -#: ../lib/modules/windowsUser.inc:1772 ../lib/modules/nisnetgroup.inc:399 -#: ../lib/modules/groupOfNamesUser.inc:169 ../lib/modules/windowsGroup.inc:512 +#: ../lib/modules/posixAccount.inc:1783 ../lib/modules/posixAccount.inc:1814 +#: ../lib/modules/windowsUser.inc:1794 ../lib/modules/nisnetgroup.inc:411 +#: ../lib/modules/groupOfNamesUser.inc:169 ../lib/modules/windowsGroup.inc:528 msgid "Available groups" msgstr "Verfügbare Gruppen" @@ -1840,11 +1837,11 @@ msgstr "Verfügbare Module" msgid "Available roles" msgstr "Verfügbare Rollen" -#: ../lib/modules/posixGroup.inc:326 ../lib/modules/asteriskExtension.inc:461 +#: ../lib/modules/posixGroup.inc:321 ../lib/modules/asteriskExtension.inc:459 msgid "Available users" msgstr "Verfügbare Benutzer" -#: ../lib/modules/sambaSamAccount.inc:1304 +#: ../lib/modules/sambaSamAccount.inc:1368 msgid "Available workstations" msgstr "Verfügbare PCs" @@ -1853,28 +1850,28 @@ msgid "B-Node (0x01)" msgstr "B-Knoten (0x01)" #: ../lib/passwordExpirationJob.inc:86 ../lib/passwordExpirationJob.inc:140 -#: ../help/help.inc:398 +#: ../help/help.inc:404 msgid "BCC address" msgstr "BCC-Adresse" #: ../templates/config/jobList.php:174 #: ../templates/upload/massBuildAccounts.php:292 -#: ../lib/modules/zarafaContact.inc:474 ../lib/modules/posixGroup.inc:400 +#: ../lib/modules/zarafaContact.inc:474 ../lib/modules/posixGroup.inc:349 #: ../lib/modules/zarafaGroup.inc:489 ../lib/modules/device.inc:295 -#: ../lib/modules/kopanoGroup.inc:440 ../lib/modules/sambaSamAccount.inc:1321 -#: ../lib/modules/sambaSamAccount.inc:1601 ../lib/modules/posixAccount.inc:1913 -#: ../lib/modules/posixAccount.inc:1990 ../lib/modules/zarafaUser.inc:978 -#: ../lib/modules/zarafaUser.inc:1027 ../lib/modules/kopanoContact.inc:427 -#: ../lib/modules/windowsUser.inc:1855 ../lib/modules/windowsUser.inc:2001 -#: ../lib/modules/sambaGroupMapping.inc:488 ../lib/modules/pykotaUser.inc:583 -#: ../lib/modules/pykotaUser.inc:640 ../lib/modules/nisnetgroup.inc:413 -#: ../lib/modules/inetOrgPerson.inc:1696 ../lib/modules/inetOrgPerson.inc:1788 -#: ../lib/modules/inetOrgPerson.inc:1873 ../lib/modules/kopanoUser.inc:978 -#: ../lib/modules/kopanoUser.inc:1027 ../lib/modules/qmailGroup.inc:839 +#: ../lib/modules/kopanoGroup.inc:440 ../lib/modules/sambaSamAccount.inc:1372 +#: ../lib/modules/sambaSamAccount.inc:1651 ../lib/modules/posixAccount.inc:1888 +#: ../lib/modules/posixAccount.inc:1956 ../lib/modules/zarafaUser.inc:978 +#: ../lib/modules/zarafaUser.inc:1027 ../lib/modules/kopanoContact.inc:424 +#: ../lib/modules/windowsUser.inc:1835 ../lib/modules/windowsUser.inc:1979 +#: ../lib/modules/sambaGroupMapping.inc:485 ../lib/modules/pykotaUser.inc:581 +#: ../lib/modules/pykotaUser.inc:624 ../lib/modules/nisnetgroup.inc:414 +#: ../lib/modules/inetOrgPerson.inc:1697 ../lib/modules/inetOrgPerson.inc:1789 +#: ../lib/modules/inetOrgPerson.inc:1874 ../lib/modules/kopanoUser.inc:899 +#: ../lib/modules/kopanoUser.inc:947 ../lib/modules/qmailGroup.inc:690 #: ../lib/modules/organizationalRole.inc:377 #: ../lib/modules/organizationalRole.inc:439 -#: ../lib/modules/windowsGroup.inc:558 ../lib/modules/windowsGroup.inc:727 -#: ../lib/modules/windowsGroup.inc:784 ../lib/modules/groupOfNames.inc:425 +#: ../lib/modules/windowsGroup.inc:532 ../lib/modules/windowsGroup.inc:707 +#: ../lib/modules/windowsGroup.inc:764 ../lib/modules/groupOfNames.inc:425 #: ../lib/modules/groupOfNames.inc:574 ../lib/modules/groupOfNames.inc:644 msgid "Back" msgstr "Zurück" @@ -1987,7 +1984,7 @@ msgstr "Nach oben" msgid "Backends" msgstr "Backends" -#: ../help/help.inc:317 +#: ../help/help.inc:321 msgid "Background color for self service pages." msgstr "Hintergrundfarbe für SelfService-Seiten." @@ -1997,9 +1994,9 @@ msgstr "Hintergrundfarbe für SelfService-Seiten." #: ../lib/modules/passwordSelfReset.inc:259 #: ../lib/modules/passwordSelfReset.inc:269 #: ../lib/modules/passwordSelfReset.inc:306 -#: ../lib/modules/passwordSelfReset.inc:367 -#: ../lib/modules/passwordSelfReset.inc:507 -#: ../lib/modules/passwordSelfReset.inc:824 +#: ../lib/modules/passwordSelfReset.inc:366 +#: ../lib/modules/passwordSelfReset.inc:505 +#: ../lib/modules/passwordSelfReset.inc:822 msgid "Backup email" msgstr "Backup-Email" @@ -2019,11 +2016,11 @@ msgstr "Backup-Skript" #: ../lib/modules/pykotaUser.inc:65 ../lib/modules/pykotaUser.inc:135 #: ../lib/modules/pykotaUser.inc:176 ../lib/modules/pykotaUser.inc:218 #: ../lib/modules/pykotaUser.inc:239 ../lib/modules/pykotaUser.inc:285 -#: ../lib/modules/pykotaUser.inc:329 ../lib/modules/pykotaUser.inc:916 -#: ../lib/modules/pykotaUser.inc:968 ../lib/modules/pykotaBillingCode.inc:85 -#: ../lib/modules/pykotaBillingCode.inc:117 -#: ../lib/modules/pykotaBillingCode.inc:143 -#: ../lib/modules/pykotaBillingCode.inc:259 +#: ../lib/modules/pykotaUser.inc:328 ../lib/modules/pykotaUser.inc:898 +#: ../lib/modules/pykotaUser.inc:950 ../lib/modules/pykotaBillingCode.inc:84 +#: ../lib/modules/pykotaBillingCode.inc:116 +#: ../lib/modules/pykotaBillingCode.inc:142 +#: ../lib/modules/pykotaBillingCode.inc:261 msgid "Balance" msgstr "Kontostand" @@ -2081,18 +2078,23 @@ msgstr "Basis (nur Basis-DN)" #: ../templates/3rdParty/pla/lib/QueryRender.php:106 #: ../templates/3rdParty/pla/lib/QueryRender.php:479 #: ../templates/3rdParty/pla/htdocs/export_form.php:59 ../lib/export.inc:127 -#: ../lib/export.inc:254 ../help/help.inc:361 +#: ../lib/export.inc:254 ../help/help.inc:367 msgid "Base DN" msgstr "Basis-DN" -#: ../templates/config/confmain.php:486 -#: ../templates/selfService/adminMain.php:473 -#: ../templates/selfService/adminMain.php:506 ../help/help.inc:136 -#: ../help/help.inc:300 +#: ../templates/config/confmain.php:496 +#: ../templates/selfService/adminMain.php:479 +#: ../templates/selfService/adminMain.php:515 ../help/help.inc:136 +#: ../help/help.inc:302 msgid "Base URL" msgstr "Basis-URL" -#: ../templates/selfService/adminMain.php:583 ../help/help.inc:316 +#: ../templates/config/confmain.php:499 +#: ../templates/selfService/adminMain.php:518 ../help/help.inc:304 +msgid "Base URLs" +msgstr "Basis-URLs" + +#: ../templates/selfService/adminMain.php:595 ../help/help.inc:320 msgid "Base color" msgstr "Basisfarbe" @@ -2101,22 +2103,22 @@ msgid "Base module" msgstr "Basismodul" #: ../lib/types/pykotaBillingCodeType.inc:94 -#: ../lib/modules/pykotaBillingCode.inc:77 -#: ../lib/modules/pykotaBillingCode.inc:115 -#: ../lib/modules/pykotaBillingCode.inc:127 -#: ../lib/modules/pykotaBillingCode.inc:129 -#: ../lib/modules/pykotaBillingCode.inc:141 -#: ../lib/modules/pykotaBillingCode.inc:257 +#: ../lib/modules/pykotaBillingCode.inc:76 +#: ../lib/modules/pykotaBillingCode.inc:114 +#: ../lib/modules/pykotaBillingCode.inc:126 +#: ../lib/modules/pykotaBillingCode.inc:128 +#: ../lib/modules/pykotaBillingCode.inc:140 +#: ../lib/modules/pykotaBillingCode.inc:259 msgid "Billing code" msgstr "Rechnungscode" +#: ../lib/modules/pykotaBillingCode.inc:128 #: ../lib/modules/pykotaBillingCode.inc:129 -#: ../lib/modules/pykotaBillingCode.inc:130 msgid "Billing code already exists!" msgstr "Dieser Rechnungscode wird bereits verwendet." +#: ../lib/modules/pykotaBillingCode.inc:126 #: ../lib/modules/pykotaBillingCode.inc:127 -#: ../lib/modules/pykotaBillingCode.inc:128 msgid "" "Billing code contains invalid characters. Valid characters are: a-z, A-Z, " "0-9 and .-_ !" @@ -2129,11 +2131,11 @@ msgstr "" msgid "Billing code count: %s" msgstr "Anzahl Rechnungscodes: %s" -#: ../lib/modules/pykotaBillingCode.inc:82 +#: ../lib/modules/pykotaBillingCode.inc:81 msgid "Billing code description." msgstr "Beschreibung des Rechnungscodes." -#: ../lib/modules/pykotaBillingCode.inc:78 +#: ../lib/modules/pykotaBillingCode.inc:77 msgid "" "Billing code name which should be created. Valid characters are: a-z, A-Z, " "0-9 and .-_ ." @@ -2162,11 +2164,11 @@ msgstr "Bind DNS" msgid "Bind DNS entries" msgstr "Bind DNS-Einträge" -#: ../templates/config/jobs.php:152 ../templates/config/confmain.php:450 +#: ../templates/config/jobs.php:152 ../templates/config/confmain.php:454 msgid "Bind password" msgstr "Bind-Passwort" -#: ../templates/config/jobs.php:147 ../templates/config/confmain.php:448 +#: ../templates/config/jobs.php:147 ../templates/config/confmain.php:452 msgid "Bind user" msgstr "Bind-Benutzer" @@ -2210,14 +2212,14 @@ msgid "Block soft quota must be smaller than block hard quota." msgstr "Weiches Block-Quota muss kleiner sein als hartes Block-Quota." #: ../templates/lists/changePassword.php:326 -#: ../templates/config/confmain.php:342 +#: ../templates/config/confmain.php:346 msgid "Both" msgstr "Beide" #: ../lib/modules/qmailGroup.inc:108 ../lib/modules/qmailGroup.inc:112 #: ../lib/modules/qmailGroup.inc:228 ../lib/modules/qmailGroup.inc:340 -#: ../lib/modules/qmailGroup.inc:396 ../lib/modules/qmailGroup.inc:487 -#: ../lib/modules/qmailGroup.inc:976 ../lib/modules/qmailGroup.inc:1013 +#: ../lib/modules/qmailGroup.inc:396 ../lib/modules/qmailGroup.inc:429 +#: ../lib/modules/qmailGroup.inc:827 ../lib/modules/qmailGroup.inc:864 msgid "Bounce admin email" msgstr "Email-Adresse für Rückläufer" @@ -2244,14 +2246,14 @@ msgstr "Die folgenden DNs gleichzeitig aktualisieren" #: ../lib/modules/windowsUser.inc:318 ../lib/modules/windowsUser.inc:322 #: ../lib/modules/windowsUser.inc:714 ../lib/modules/windowsUser.inc:794 #: ../lib/modules/windowsUser.inc:904 ../lib/modules/windowsUser.inc:1039 -#: ../lib/modules/windowsUser.inc:1181 ../lib/modules/windowsUser.inc:2745 -#: ../lib/modules/windowsUser.inc:3645 ../lib/modules/inetOrgPerson.inc:107 +#: ../lib/modules/windowsUser.inc:1180 ../lib/modules/windowsUser.inc:2717 +#: ../lib/modules/windowsUser.inc:3617 ../lib/modules/inetOrgPerson.inc:107 #: ../lib/modules/inetOrgPerson.inc:163 ../lib/modules/inetOrgPerson.inc:299 #: ../lib/modules/inetOrgPerson.inc:540 ../lib/modules/inetOrgPerson.inc:705 #: ../lib/modules/inetOrgPerson.inc:709 ../lib/modules/inetOrgPerson.inc:1483 -#: ../lib/modules/inetOrgPerson.inc:1486 ../lib/modules/inetOrgPerson.inc:1965 -#: ../lib/modules/inetOrgPerson.inc:2060 ../lib/modules/inetOrgPerson.inc:2757 -#: ../lib/modules/inetOrgPerson.inc:3877 ../lib/modules/inetOrgPerson.inc:3913 +#: ../lib/modules/inetOrgPerson.inc:1486 ../lib/modules/inetOrgPerson.inc:1966 +#: ../lib/modules/inetOrgPerson.inc:2061 ../lib/modules/inetOrgPerson.inc:2758 +#: ../lib/modules/inetOrgPerson.inc:3878 ../lib/modules/inetOrgPerson.inc:3914 msgid "Business category" msgstr "Abteilung" @@ -2268,7 +2270,7 @@ msgstr "" "Standardmäßig zeigt LAM alle Accounts an, die zu den gewählten " "Accountmodulen passen." -#: ../help/help.inc:297 +#: ../help/help.inc:299 msgid "" "By default all modifications are done as the user that authenticated in self " "service. If active then LAM will use the connection user for all LDAP " @@ -2286,7 +2288,7 @@ msgstr "" "Standardmäßig werden alle Benutzer im Suffix des SelfService erstellt. Sie " "können hier einen alternativen Suffix angeben." -#: ../lib/modules/customScripts.inc:89 +#: ../lib/modules/customScripts.inc:88 msgid "" "By default, LAM will show the executed command along with any output of it. " "If you want to hide the command and only show the output then activate this " @@ -2300,7 +2302,7 @@ msgid "Bytes sent" msgstr "Bytes gesendet" #: ../lib/passwordExpirationJob.inc:85 ../lib/passwordExpirationJob.inc:134 -#: ../help/help.inc:394 +#: ../help/help.inc:400 msgid "CC address" msgstr "CC-Adresse" @@ -2327,8 +2329,8 @@ msgstr "Cache Zeitbegrenzung" #: ../lib/modules/asteriskAccount.inc:270 #: ../lib/modules/asteriskAccount.inc:353 #: ../lib/modules/asteriskAccount.inc:613 -#: ../lib/modules/asteriskAccount.inc:987 -#: ../lib/modules/asteriskAccount.inc:1231 +#: ../lib/modules/asteriskAccount.inc:988 +#: ../lib/modules/asteriskAccount.inc:1232 msgid "Call groups" msgstr "Anrufergruppen" @@ -2336,14 +2338,14 @@ msgstr "Anrufergruppen" #: ../lib/modules/asteriskAccount.inc:264 #: ../lib/modules/asteriskAccount.inc:302 #: ../lib/modules/asteriskAccount.inc:587 -#: ../lib/modules/asteriskAccount.inc:981 +#: ../lib/modules/asteriskAccount.inc:982 msgid "Caller ID" msgstr "Anrufer-ID" #: ../lib/modules/sambaSamAccount.inc:308 #: ../lib/modules/sambaSamAccount.inc:311 #: ../lib/modules/sambaSamAccount.inc:314 -#: ../lib/modules/sambaSamAccount.inc:320 ../lib/modules/posixAccount.inc:435 +#: ../lib/modules/sambaSamAccount.inc:320 ../lib/modules/posixAccount.inc:440 #: ../lib/modules/organizationalRoleUser.inc:61 #: ../lib/modules/windowsUser.inc:227 ../lib/modules/windowsUser.inc:231 #: ../lib/modules/sambaGroupMapping.inc:159 @@ -2357,8 +2359,8 @@ msgstr "Kann leer bleiben." #: ../lib/modules/asteriskAccount.inc:292 #: ../lib/modules/asteriskAccount.inc:513 #: ../lib/modules/asteriskAccount.inc:739 -#: ../lib/modules/asteriskAccount.inc:1009 -#: ../lib/modules/asteriskAccount.inc:1253 +#: ../lib/modules/asteriskAccount.inc:1010 +#: ../lib/modules/asteriskAccount.inc:1254 msgid "Can call forward" msgstr "Kann weiterleiten" @@ -2376,14 +2378,14 @@ msgstr "Kann weiterleiten" #: ../templates/config/mainmanage.php:495 #: ../templates/config/confmodules.php:153 ../templates/config/jobList.php:185 #: ../templates/config/jobs.php:273 ../templates/config/jobs.php:316 -#: ../templates/config/confmain.php:524 +#: ../templates/config/confmain.php:539 #: ../templates/config/moduleSettings.php:187 #: ../templates/config/conftypes.php:316 #: ../templates/selfService/profManage.php:196 #: ../templates/selfService/profManage.php:213 #: ../templates/selfService/profManage.php:228 #: ../templates/selfService/selfService2Factor.php:188 -#: ../templates/selfService/adminMain.php:812 +#: ../templates/selfService/adminMain.php:824 #: ../templates/pdfedit/pdfpage.php:450 ../templates/pdfedit/pdfmain.php:249 #: ../templates/pdfedit/pdfmain.php:258 ../templates/pdfedit/pdfmain.php:266 #: ../templates/delete.php:155 ../templates/profedit/profilepage.php:245 @@ -2396,42 +2398,42 @@ msgstr "Kann weiterleiten" #: ../templates/3rdParty/pla/htdocs/create_confirm.php:121 #: ../templates/3rdParty/pla/htdocs/mass_delete.php:119 #: ../templates/initsuff.php:202 ../templates/login2Factor.php:176 -#: ../lib/types/automountType.inc:213 ../lib/types/user.inc:356 -#: ../lib/types/bind.inc:157 ../lib/html.inc:603 -#: ../lib/modules/locking389ds.inc:168 ../lib/modules/pykotaPrinter.inc:430 -#: ../lib/modules/mitKerberos.inc:694 ../lib/modules/autoDelete.inc:201 -#: ../lib/modules/qmailUser.inc:883 ../lib/modules/zarafaContact.inc:423 -#: ../lib/modules/windowsHost.inc:226 ../lib/modules/zarafaGroup.inc:437 -#: ../lib/modules/device.inc:245 ../lib/modules/kopanoGroup.inc:389 -#: ../lib/modules/sambaSamAccount.inc:1393 -#: ../lib/modules/sambaSamAccount.inc:1439 -#: ../lib/modules/sambaSamAccount.inc:1525 ../lib/modules/zarafaUser.inc:921 -#: ../lib/modules/kopanoContact.inc:376 ../lib/modules/windowsUser.inc:1680 -#: ../lib/modules/windowsUser.inc:2135 ../lib/modules/sambaGroupMapping.inc:467 -#: ../lib/modules/shadowAccount.inc:494 ../lib/modules/shadowAccount.inc:547 -#: ../lib/modules/nisnetgroup.inc:507 ../lib/modules/inetOrgPerson.inc:1754 -#: ../lib/modules/freeRadius.inc:568 ../lib/modules/kopanoUser.inc:921 +#: ../lib/types/automountType.inc:213 ../lib/types/user.inc:357 +#: ../lib/types/bind.inc:157 ../lib/html.inc:610 +#: ../lib/modules/locking389ds.inc:185 ../lib/modules/pykotaPrinter.inc:422 +#: ../lib/modules/mitKerberos.inc:717 ../lib/modules/nisMailAliasUser.inc:389 +#: ../lib/modules/autoDelete.inc:211 ../lib/modules/qmailUser.inc:724 +#: ../lib/modules/zarafaContact.inc:423 ../lib/modules/windowsHost.inc:229 +#: ../lib/modules/zarafaGroup.inc:437 ../lib/modules/device.inc:245 +#: ../lib/modules/kopanoGroup.inc:389 ../lib/modules/sambaSamAccount.inc:1438 +#: ../lib/modules/sambaSamAccount.inc:1499 +#: ../lib/modules/sambaSamAccount.inc:1585 ../lib/modules/zarafaUser.inc:921 +#: ../lib/modules/kopanoContact.inc:375 ../lib/modules/windowsUser.inc:1692 +#: ../lib/modules/windowsUser.inc:2107 ../lib/modules/sambaGroupMapping.inc:466 +#: ../lib/modules/shadowAccount.inc:509 ../lib/modules/shadowAccount.inc:575 +#: ../lib/modules/nisnetgroup.inc:493 ../lib/modules/inetOrgPerson.inc:1755 +#: ../lib/modules/freeRadius.inc:568 ../lib/modules/kopanoUser.inc:844 #: ../lib/modules/customFields.inc:1244 ../lib/modules/aliasEntry.inc:157 -#: ../lib/modules/aliasEntry.inc:180 ../lib/modules/qmailGroup.inc:790 +#: ../lib/modules/aliasEntry.inc:180 ../lib/modules/qmailGroup.inc:643 #: ../lib/modules/organizationalRole.inc:305 ../lib/modules/fixed_ip.inc:609 -#: ../lib/modules/heimdalKerberos.inc:615 ../lib/modules/windowsGroup.inc:615 -#: ../lib/modules/windowsGroup.inc:678 ../lib/modules/groupOfNames.inc:355 -#: ../lib/modules/groupOfNames.inc:504 ../lib/modules/nisMailAlias.inc:384 -#: ../lib/lists.inc:819 ../lib/lists.inc:900 ../lib/modules.inc:1308 +#: ../lib/modules/heimdalKerberos.inc:615 ../lib/modules/windowsGroup.inc:591 +#: ../lib/modules/windowsGroup.inc:657 ../lib/modules/groupOfNames.inc:355 +#: ../lib/modules/groupOfNames.inc:504 ../lib/modules/nisMailAlias.inc:389 +#: ../lib/lists.inc:808 ../lib/lists.inc:891 ../lib/modules.inc:1312 msgid "Cancel" msgstr "Abbrechen" #: ../templates/login.php:513 ../templates/login.php:583 #: ../templates/config/jobs.php:429 -#: ../templates/selfService/selfServiceLogin.php:195 -#: ../templates/selfService/selfServiceLogin.php:201 -#: ../templates/selfService/selfServiceMain.php:170 +#: ../templates/selfService/selfServiceLogin.php:196 +#: ../templates/selfService/selfServiceLogin.php:202 +#: ../templates/selfService/selfServiceMain.php:171 #: ../lib/modules/selfRegistration.inc:824 msgid "Cannot connect to specified LDAP server. Please try again." msgstr "" "Kann keine Verbindung zum LDAP-Server aufbauen. Bitte erneut versuchen." -#: ../templates/login.php:281 ../lib/config.inc:2525 +#: ../templates/login.php:281 ../lib/config.inc:2784 msgid "Cannot open config file!" msgstr "Kann Konfigurationsdatei nicht öffnen!" @@ -2439,7 +2441,7 @@ msgstr "Kann Konfigurationsdatei nicht öffnen!" msgid "Cannot update quota." msgstr "Kann Quota nicht ändern." -#: ../lib/config.inc:2537 ../lib/config.inc:2545 +#: ../lib/config.inc:2796 ../lib/config.inc:2804 msgid "" "Cannot write certificate file. Please check the permissions of config/" "serverCerts.pem." @@ -2452,71 +2454,71 @@ msgstr "" #: ../lib/modules/zarafaUser.inc:575 ../lib/modules/zarafaUser.inc:1273 #: ../lib/modules/zarafaUser.inc:1827 ../lib/modules/kopanoUser.inc:138 #: ../lib/modules/kopanoUser.inc:269 ../lib/modules/kopanoUser.inc:387 -#: ../lib/modules/kopanoUser.inc:438 ../lib/modules/kopanoUser.inc:575 -#: ../lib/modules/kopanoUser.inc:1273 ../lib/modules/kopanoUser.inc:1838 +#: ../lib/modules/kopanoUser.inc:438 ../lib/modules/kopanoUser.inc:540 +#: ../lib/modules/kopanoUser.inc:1192 ../lib/modules/kopanoUser.inc:1757 msgid "Capacity" msgstr "Kapazität" #: ../templates/selfService/selfServiceLogin.php:116 -#: ../templates/selfService/selfServiceLogin.php:302 -#: ../templates/selfService/adminMain.php:528 -#: ../templates/selfService/adminMain.php:533 +#: ../templates/selfService/selfServiceLogin.php:303 +#: ../templates/selfService/adminMain.php:540 +#: ../templates/selfService/adminMain.php:545 #: ../lib/modules/selfRegistration.inc:126 #: ../lib/modules/selfRegistration.inc:227 #: ../lib/modules/selfRegistration.inc:455 #: ../lib/modules/selfRegistration.inc:529 #: ../lib/modules/passwordSelfReset.inc:285 -#: ../lib/modules/passwordSelfReset.inc:686 -#: ../lib/modules/passwordSelfReset.inc:1076 -#: ../lib/modules/passwordSelfReset.inc:1141 +#: ../lib/modules/passwordSelfReset.inc:684 +#: ../lib/modules/passwordSelfReset.inc:1074 +#: ../lib/modules/passwordSelfReset.inc:1139 msgid "Captcha" msgstr "Captcha" -#: ../templates/config/confmain.php:497 -#: ../templates/selfService/adminMain.php:517 ../help/help.inc:306 +#: ../templates/config/confmain.php:512 +#: ../templates/selfService/adminMain.php:529 ../help/help.inc:310 msgid "Caption" msgstr "Überschrift" -#: ../templates/selfService/adminMain.php:549 +#: ../templates/selfService/adminMain.php:561 msgid "Captions and labels" msgstr "Überschriften und Beschriftungen" #: ../lib/modules/windowsUser.inc:306 ../lib/modules/windowsUser.inc:690 -#: ../lib/modules/windowsUser.inc:895 ../lib/modules/windowsUser.inc:1172 -#: ../lib/modules/windowsUser.inc:2742 ../lib/modules/windowsUser.inc:3642 +#: ../lib/modules/windowsUser.inc:895 ../lib/modules/windowsUser.inc:1171 +#: ../lib/modules/windowsUser.inc:2714 ../lib/modules/windowsUser.inc:3614 #: ../lib/modules/inetOrgPerson.inc:162 ../lib/modules/inetOrgPerson.inc:461 #: ../lib/modules/inetOrgPerson.inc:534 ../lib/modules/inetOrgPerson.inc:729 #: ../lib/modules/inetOrgPerson.inc:1459 ../lib/modules/inetOrgPerson.inc:1462 -#: ../lib/modules/inetOrgPerson.inc:1967 ../lib/modules/inetOrgPerson.inc:2735 -#: ../lib/modules/inetOrgPerson.inc:3875 ../lib/modules/inetOrgPerson.inc:3912 +#: ../lib/modules/inetOrgPerson.inc:1968 ../lib/modules/inetOrgPerson.inc:2736 +#: ../lib/modules/inetOrgPerson.inc:3876 ../lib/modules/inetOrgPerson.inc:3913 msgid "Car license" msgstr "Führerschein" -#: ../templates/selfService/adminMain.php:601 -#: ../lib/modules/locking389ds.inc:84 ../lib/modules/locking389ds.inc:164 -#: ../lib/modules/mitKerberos.inc:368 ../lib/modules/mitKerberos.inc:379 -#: ../lib/modules/mitKerberos.inc:690 ../lib/modules/autoDelete.inc:129 -#: ../lib/modules/autoDelete.inc:200 ../lib/modules/qmailUser.inc:626 -#: ../lib/modules/qmailUser.inc:879 ../lib/modules/zarafaContact.inc:243 -#: ../lib/modules/windowsHost.inc:155 ../lib/modules/windowsHost.inc:225 +#: ../templates/selfService/adminMain.php:613 +#: ../lib/modules/locking389ds.inc:84 ../lib/modules/locking389ds.inc:179 +#: ../lib/modules/mitKerberos.inc:369 ../lib/modules/mitKerberos.inc:381 +#: ../lib/modules/mitKerberos.inc:711 ../lib/modules/autoDelete.inc:129 +#: ../lib/modules/autoDelete.inc:210 ../lib/modules/qmailUser.inc:516 +#: ../lib/modules/qmailUser.inc:718 ../lib/modules/zarafaContact.inc:243 +#: ../lib/modules/windowsHost.inc:157 ../lib/modules/windowsHost.inc:228 #: ../lib/modules/zarafaGroup.inc:242 ../lib/modules/device.inc:167 -#: ../lib/modules/kopanoGroup.inc:221 ../lib/modules/sambaSamAccount.inc:1137 -#: ../lib/modules/sambaSamAccount.inc:1435 ../lib/modules/zarafaUser.inc:491 +#: ../lib/modules/kopanoGroup.inc:221 ../lib/modules/sambaSamAccount.inc:1173 +#: ../lib/modules/sambaSamAccount.inc:1493 ../lib/modules/zarafaUser.inc:491 #: ../lib/modules/zarafaUser.inc:587 ../lib/modules/kopanoContact.inc:227 -#: ../lib/modules/windowsUser.inc:1204 ../lib/modules/windowsUser.inc:1231 -#: ../lib/modules/windowsUser.inc:1676 ../lib/modules/windowsUser.inc:2131 -#: ../lib/modules/shadowAccount.inc:359 ../lib/modules/shadowAccount.inc:372 -#: ../lib/modules/shadowAccount.inc:490 ../lib/modules/shadowAccount.inc:543 +#: ../lib/modules/windowsUser.inc:1203 ../lib/modules/windowsUser.inc:1231 +#: ../lib/modules/windowsUser.inc:1686 ../lib/modules/windowsUser.inc:2103 +#: ../lib/modules/shadowAccount.inc:361 ../lib/modules/shadowAccount.inc:374 +#: ../lib/modules/shadowAccount.inc:503 ../lib/modules/shadowAccount.inc:569 #: ../lib/modules/inetOrgPerson.inc:1538 ../lib/modules/freeRadius.inc:371 -#: ../lib/modules/freeRadius.inc:562 ../lib/modules/kopanoUser.inc:491 -#: ../lib/modules/kopanoUser.inc:587 ../lib/modules/asteriskExtension.inc:248 -#: ../lib/modules/aliasEntry.inc:101 ../lib/modules/qmailGroup.inc:537 +#: ../lib/modules/freeRadius.inc:562 ../lib/modules/kopanoUser.inc:474 +#: ../lib/modules/kopanoUser.inc:551 ../lib/modules/asteriskExtension.inc:248 +#: ../lib/modules/aliasEntry.inc:101 ../lib/modules/qmailGroup.inc:451 #: ../lib/modules/organizationalRole.inc:176 #: ../lib/modules/passwordSelfReset.inc:360 #: ../lib/modules/heimdalKerberos.inc:316 #: ../lib/modules/heimdalKerberos.inc:327 -#: ../lib/modules/heimdalKerberos.inc:611 ../lib/modules/windowsGroup.inc:357 -#: ../lib/modules/windowsGroup.inc:614 ../lib/modules/groupOfNames.inc:218 +#: ../lib/modules/heimdalKerberos.inc:611 ../lib/modules/windowsGroup.inc:355 +#: ../lib/modules/windowsGroup.inc:589 ../lib/modules/groupOfNames.inc:218 #: ../lib/modules/groupOfNames.inc:227 msgid "Change" msgstr "Ändern" @@ -2529,7 +2531,7 @@ msgstr "Asterisk-Passwort ändern" msgid "Change Asterisk voicemail password" msgstr "Asterisk-Anrufbeantworterpasswort ändern" -#: ../lib/modules/posixGroup.inc:249 ../lib/modules/posixGroup.inc:501 +#: ../lib/modules/posixGroup.inc:232 ../lib/modules/posixGroup.inc:450 msgid "Change GID number of users and hosts" msgstr "Ändere die GID-Nummern aller Benutzer auf den neuen Wert" @@ -2554,7 +2556,7 @@ msgstr "Unix-Passwort ändern" msgid "Change Windows password" msgstr "Windows-Passwort ändern" -#: ../lib/types/user.inc:356 +#: ../lib/types/user.inc:357 msgid "Change account status" msgstr "Accountstatus ändern" @@ -2563,11 +2565,11 @@ msgstr "Accountstatus ändern" msgid "Change default profile" msgstr "Standardprofil wechseln" -#: ../templates/selfService/adminMain.php:656 +#: ../templates/selfService/adminMain.php:668 msgid "Change field label." msgstr "Feldbezeichner ändern." -#: ../lib/lists.inc:900 +#: ../lib/lists.inc:891 msgid "Change list settings" msgstr "Listeneinstellungen ändern" @@ -2580,7 +2582,7 @@ msgstr "Hauptpasswort ändern" #: ../templates/lists/changePassword.php:337 #: ../templates/lists/changePassword.php:339 #: ../templates/lists/changePassword.php:374 -#: ../templates/lists/changePassword.php:376 ../lib/types/user.inc:809 +#: ../templates/lists/changePassword.php:376 ../lib/types/user.inc:810 #: ../help/help.inc:109 msgid "Change password" msgstr "Passwort ändern" @@ -2590,15 +2592,15 @@ msgstr "Passwort ändern" msgid "Change password now?" msgstr "Passwort jetzt ändern?" -#: ../templates/config/confmain.php:212 +#: ../templates/config/confmain.php:216 msgid "Change passwords" msgstr "Passwörter ändern" -#: ../lib/lists.inc:901 +#: ../lib/lists.inc:892 msgid "Change settings" msgstr "Einstellungen ändern" -#: ../lib/modules/posixAccount.inc:1210 +#: ../lib/modules/posixAccount.inc:1190 msgid "Changed value because only ASCII characters are allowed." msgstr "Wert wurde geändert da nur ASCII-Zeichen erlaubt sind." @@ -2608,7 +2610,7 @@ msgstr "Wert wurde geändert da nur ASCII-Zeichen erlaubt sind." msgid "Charlie" msgstr "Charlie" -#: ../lib/modules/posixAccount.inc:1690 +#: ../lib/modules/posixAccount.inc:1671 msgid "Check home directories" msgstr "Heimatverzeichnisse prüfen" @@ -2657,8 +2659,8 @@ msgstr "Einfaches Chinesisch" msgid "Chinese Traditional" msgstr "Traditionelles Chinesisch" -#: ../templates/3rdParty/pla/htdocs/entry_chooser.php:23 ../lib/html.inc:601 -#: ../lib/html.inc:602 +#: ../templates/3rdParty/pla/htdocs/entry_chooser.php:23 ../lib/html.inc:608 +#: ../lib/html.inc:609 msgid "Choose entry" msgstr "Eintrag wählen" @@ -2674,25 +2676,25 @@ msgstr "" #: ../lib/modules/puppetClient.inc:99 ../lib/modules/puppetClient.inc:103 #: ../lib/modules/puppetClient.inc:151 ../lib/modules/puppetClient.inc:167 -#: ../lib/modules/puppetClient.inc:180 ../lib/modules/puppetClient.inc:282 -#: ../lib/modules/puppetClient.inc:285 ../lib/modules/puppetClient.inc:406 +#: ../lib/modules/puppetClient.inc:180 ../lib/modules/puppetClient.inc:279 +#: ../lib/modules/puppetClient.inc:282 ../lib/modules/puppetClient.inc:402 msgid "Classes" msgstr "Klassen" -#: ../lib/modules/qmailUser.inc:1251 ../lib/modules/windowsUser.inc:4112 -#: ../lib/modules/shadowAccount.inc:1045 ../lib/modules/freeRadius.inc:816 +#: ../lib/modules/qmailUser.inc:1091 ../lib/modules/windowsUser.inc:4084 +#: ../lib/modules/shadowAccount.inc:1072 ../lib/modules/freeRadius.inc:816 msgid "Cleanup expired user accounts" msgstr "Abgelaufene Benutzerkonten löschen" -#: ../lib/lists.inc:474 +#: ../lib/lists.inc:462 msgid "Clear filter" msgstr "Suchfilter löschen" -#: ../lib/types/user.inc:511 +#: ../lib/types/user.inc:512 msgid "Clear password expiration" msgstr "Passwortablauf zurücksetzen" -#: ../lib/config.inc:340 +#: ../lib/config.inc:353 msgid "Click here if you are not directed to the next page." msgstr "Hier klicken wenn Sie nicht zur nächsten Seite weitergeleitet werden." @@ -2708,15 +2710,15 @@ msgstr "Hiermit können Sie das Datum grafisch auswählen" msgid "Click to popup a dialog to select an entry (DN) graphically" msgstr "Hiermit können Sie den DN grafisch auswählen" -#: ../templates/config/confmain.php:489 -#: ../templates/selfService/adminMain.php:509 ../help/help.inc:318 +#: ../templates/config/confmain.php:502 +#: ../templates/selfService/adminMain.php:521 ../help/help.inc:322 msgid "Client id" msgstr "Client ID" #: ../lib/modules/sambaSamAccount.inc:243 #: ../lib/modules/sambaSamAccount.inc:380 -#: ../lib/modules/sambaSamAccount.inc:1493 -#: ../lib/modules/sambaSamAccount.inc:1967 +#: ../lib/modules/sambaSamAccount.inc:1554 +#: ../lib/modules/sambaSamAccount.inc:2014 msgid "Client printer is default" msgstr "Client-Drucker ist Standarddrucker" @@ -2745,19 +2747,19 @@ msgstr "" msgid "Comma separated list of services (e.g. sshd, imap, ftp)." msgstr "Komma-getrennte Liste der erlaubten Dienste (z.B. sshd, imap, ftp)." -#: ../lib/types/sudo.inc:83 ../lib/modules/customScripts.inc:156 +#: ../lib/types/sudo.inc:83 ../lib/modules/customScripts.inc:155 msgid "Command" msgstr "Kommando" #: ../lib/modules/sudoRole.inc:81 ../lib/modules/sudoRole.inc:107 #: ../lib/modules/sudoRole.inc:170 ../lib/modules/sudoRole.inc:217 -#: ../lib/modules/sudoRole.inc:229 ../lib/modules/sudoRole.inc:369 -#: ../lib/modules/sudoRole.inc:749 +#: ../lib/modules/sudoRole.inc:229 ../lib/modules/sudoRole.inc:304 +#: ../lib/modules/sudoRole.inc:456 msgid "Commands" msgstr "Kommandos" -#: ../lib/modules/pykotaUser.inc:339 ../lib/modules/pykotaUser.inc:564 -#: ../lib/modules/pykotaUser.inc:924 ../lib/modules/pykotaUser.inc:988 +#: ../lib/modules/pykotaUser.inc:371 ../lib/modules/pykotaUser.inc:561 +#: ../lib/modules/pykotaUser.inc:906 ../lib/modules/pykotaUser.inc:970 msgid "Comment" msgstr "Kommentar" @@ -2769,29 +2771,29 @@ msgstr "Kommentar für initialen Kontostand." msgid "Commit" msgstr "Bestätigen" -#: ../lib/modules/posixAccount.inc:316 +#: ../lib/modules/posixAccount.inc:321 msgid "Common examples are \"@givenname@%sn%\" or \"%givenname%.%sn%\"." msgstr "Typische Beispiele sind \"@givenname@%sn%\" oder \"%givenname%.%sn%\"." #: ../templates/config/mainmanage.php:370 ../lib/types/host.inc:96 #: ../lib/types/user.inc:95 ../lib/types/customType.inc:71 -#: ../lib/modules/pykotaGroup.inc:107 ../lib/modules/pykotaGroup.inc:386 -#: ../lib/modules/pykotaGroup.inc:476 ../lib/modules/pykotaGroup.inc:488 +#: ../lib/modules/pykotaGroup.inc:107 ../lib/modules/pykotaGroup.inc:385 +#: ../lib/modules/pykotaGroup.inc:475 ../lib/modules/pykotaGroup.inc:487 #: ../lib/modules/posixAccount.inc:119 ../lib/modules/posixAccount.inc:175 -#: ../lib/modules/posixAccount.inc:438 ../lib/modules/posixAccount.inc:464 -#: ../lib/modules/posixAccount.inc:1634 ../lib/modules/posixAccount.inc:2119 -#: ../lib/modules/posixAccount.inc:2147 ../lib/modules/posixAccount.inc:2457 -#: ../lib/modules/posixAccount.inc:3225 ../lib/modules/windowsUser.inc:125 +#: ../lib/modules/posixAccount.inc:443 ../lib/modules/posixAccount.inc:469 +#: ../lib/modules/posixAccount.inc:1613 ../lib/modules/posixAccount.inc:2077 +#: ../lib/modules/posixAccount.inc:2105 ../lib/modules/posixAccount.inc:2415 +#: ../lib/modules/posixAccount.inc:3184 ../lib/modules/windowsUser.inc:125 #: ../lib/modules/windowsUser.inc:436 ../lib/modules/windowsUser.inc:761 #: ../lib/modules/windowsUser.inc:840 ../lib/modules/windowsUser.inc:986 -#: ../lib/modules/windowsUser.inc:1127 ../lib/modules/windowsUser.inc:2713 +#: ../lib/modules/windowsUser.inc:1126 ../lib/modules/windowsUser.inc:2685 #: ../lib/modules/pykotaUser.inc:111 ../lib/modules/pykotaUser.inc:202 -#: ../lib/modules/pykotaUser.inc:245 ../lib/modules/pykotaUser.inc:369 -#: ../lib/modules/pykotaUser.inc:904 ../lib/modules/inetOrgPerson.inc:98 +#: ../lib/modules/pykotaUser.inc:245 ../lib/modules/pykotaUser.inc:357 +#: ../lib/modules/pykotaUser.inc:886 ../lib/modules/inetOrgPerson.inc:98 #: ../lib/modules/inetOrgPerson.inc:681 ../lib/modules/inetOrgPerson.inc:1218 -#: ../lib/modules/inetOrgPerson.inc:1221 ../lib/modules/inetOrgPerson.inc:1933 -#: ../lib/modules/inetOrgPerson.inc:1962 ../lib/modules/inetOrgPerson.inc:1997 -#: ../lib/modules/inetOrgPerson.inc:2173 ../lib/modules/inetOrgPerson.inc:3922 +#: ../lib/modules/inetOrgPerson.inc:1221 ../lib/modules/inetOrgPerson.inc:1934 +#: ../lib/modules/inetOrgPerson.inc:1963 ../lib/modules/inetOrgPerson.inc:1998 +#: ../lib/modules/inetOrgPerson.inc:2174 ../lib/modules/inetOrgPerson.inc:3922 #: ../lib/modules/asteriskExtension.inc:119 msgid "Common name" msgstr "Allgemeiner Name" @@ -2806,8 +2808,8 @@ msgstr "Name der Asterisk-Erweiterung." #: ../lib/types/user.inc:96 ../lib/modules/windowsUser.inc:358 #: ../lib/modules/windowsUser.inc:649 ../lib/modules/windowsUser.inc:773 -#: ../lib/modules/windowsUser.inc:922 ../lib/modules/windowsUser.inc:1184 -#: ../lib/modules/windowsUser.inc:2731 ../lib/modules/windowsUser.inc:3646 +#: ../lib/modules/windowsUser.inc:922 ../lib/modules/windowsUser.inc:1183 +#: ../lib/modules/windowsUser.inc:2703 ../lib/modules/windowsUser.inc:3618 msgid "Company" msgstr "Firma" @@ -2858,14 +2860,14 @@ msgstr "Einstellungen" #: ../lib/modules/qmailUser.inc:146 ../lib/modules/qmailUser.inc:225 #: ../lib/modules/qmailUser.inc:349 ../lib/modules/qmailUser.inc:393 -#: ../lib/modules/qmailUser.inc:584 ../lib/modules/qmailUser.inc:1130 -#: ../lib/modules/qmailUser.inc:1225 +#: ../lib/modules/qmailUser.inc:486 ../lib/modules/qmailUser.inc:970 +#: ../lib/modules/qmailUser.inc:1065 msgid "Configuration type" msgstr "Konfigurationstyp" #: ../lib/modules/qmailGroup.inc:193 ../lib/modules/qmailGroup.inc:326 -#: ../lib/modules/qmailGroup.inc:376 ../lib/modules/qmailGroup.inc:588 -#: ../lib/modules/qmailGroup.inc:998 ../lib/modules/qmailGroup.inc:1025 +#: ../lib/modules/qmailGroup.inc:376 ../lib/modules/qmailGroup.inc:487 +#: ../lib/modules/qmailGroup.inc:849 ../lib/modules/qmailGroup.inc:876 msgid "Confirm sender" msgstr "Absender prüfen" @@ -2875,8 +2877,8 @@ msgstr "Einstellungen für Bestätigungsmail" #: ../lib/modules/qmailGroup.inc:69 ../lib/modules/qmailGroup.inc:117 #: ../lib/modules/qmailGroup.inc:236 ../lib/modules/qmailGroup.inc:343 -#: ../lib/modules/qmailGroup.inc:556 ../lib/modules/qmailGroup.inc:977 -#: ../lib/modules/qmailGroup.inc:1014 +#: ../lib/modules/qmailGroup.inc:467 ../lib/modules/qmailGroup.inc:828 +#: ../lib/modules/qmailGroup.inc:865 msgid "Confirmation text" msgstr "Prüfungstext" @@ -2894,15 +2896,15 @@ msgstr "Modulkonflikt:" #: ../lib/modules/sambaSamAccount.inc:241 #: ../lib/modules/sambaSamAccount.inc:374 -#: ../lib/modules/sambaSamAccount.inc:1489 -#: ../lib/modules/sambaSamAccount.inc:1957 +#: ../lib/modules/sambaSamAccount.inc:1550 +#: ../lib/modules/sambaSamAccount.inc:2004 msgid "Connect client drives" msgstr "Verbinde Client-Laufwerke" #: ../lib/modules/sambaSamAccount.inc:242 #: ../lib/modules/sambaSamAccount.inc:377 -#: ../lib/modules/sambaSamAccount.inc:1491 -#: ../lib/modules/sambaSamAccount.inc:1962 +#: ../lib/modules/sambaSamAccount.inc:1552 +#: ../lib/modules/sambaSamAccount.inc:2009 msgid "Connect client printers" msgstr "Verbinde Client-Drucker" @@ -2910,15 +2912,15 @@ msgstr "Verbinde Client-Drucker" msgid "Connection statistics" msgstr "Verbindungsstatistiken" -#: ../lib/types/oracleContextType.inc:81 ../lib/modules/oracleService.inc:65 -#: ../lib/modules/oracleService.inc:85 ../lib/modules/oracleService.inc:99 -#: ../lib/modules/oracleService.inc:123 ../lib/modules/oracleService.inc:167 +#: ../lib/types/oracleContextType.inc:81 ../lib/modules/oracleService.inc:64 +#: ../lib/modules/oracleService.inc:84 ../lib/modules/oracleService.inc:98 +#: ../lib/modules/oracleService.inc:122 ../lib/modules/oracleService.inc:165 msgid "Connection string" msgstr "Connection-String" #: ../lib/modules/sambaSamAccount.inc:238 -#: ../lib/modules/sambaSamAccount.inc:1477 -#: ../lib/modules/sambaSamAccount.inc:1950 +#: ../lib/modules/sambaSamAccount.inc:1538 +#: ../lib/modules/sambaSamAccount.inc:1997 msgid "Connection time limit" msgstr "Zeitlimit für Verbindung" @@ -2930,7 +2932,7 @@ msgstr "Konstante" msgid "Contact address for this zone (e.g. \"root.example.com.\")." msgstr "Kontaktadresse für diese Zone (z.B. \"root.beispiel.de.\")." -#: ../lib/modules/windowsUser.inc:1140 ../lib/modules/inetOrgPerson.inc:1382 +#: ../lib/modules/windowsUser.inc:1139 ../lib/modules/inetOrgPerson.inc:1382 msgid "Contact data" msgstr "Kontaktdaten" @@ -3037,13 +3039,13 @@ msgstr "Courier" #: ../templates/profedit/profilemain.php:206 #: ../templates/3rdParty/pla/lib/TemplateRender.php:1077 #: ../templates/3rdParty/pla/lib/TemplateRender.php:1081 -#: ../templates/initsuff.php:201 ../lib/modules/nisMailAliasUser.inc:372 -#: ../lib/modules/sambaSamAccount.inc:1580 ../lib/modules/posixAccount.inc:1969 +#: ../templates/initsuff.php:201 ../lib/modules/nisMailAliasUser.inc:364 +#: ../lib/modules/sambaSamAccount.inc:1632 ../lib/modules/posixAccount.inc:1937 msgid "Create" msgstr "Erstellen" -#: ../lib/lists.inc:582 ../lib/lists.inc:787 ../lib/modules.inc:1424 -#: ../help/help.inc:252 +#: ../lib/lists.inc:571 ../lib/lists.inc:776 ../lib/modules.inc:1430 +#: ../help/help.inc:254 msgid "Create PDF file" msgstr "PDF-Datei erstellen" @@ -3152,12 +3154,12 @@ msgstr "Weiteren Benutzer anlegen" msgid "Create another view" msgstr "Weitere Ansicht anlegen" -#: ../lib/lists.inc:798 +#: ../lib/lists.inc:787 msgid "Create for" msgstr "Erstellen für" -#: ../lib/modules/posixAccount.inc:239 ../lib/modules/posixAccount.inc:1676 -#: ../lib/modules/posixAccount.inc:2034 +#: ../lib/modules/posixAccount.inc:244 ../lib/modules/posixAccount.inc:1656 +#: ../lib/modules/posixAccount.inc:2000 msgid "Create home directory" msgstr "Heimatverzeichnis anlegen" @@ -3166,7 +3168,7 @@ msgstr "Heimatverzeichnis anlegen" msgid "Create mailbox" msgstr "Postfach erstellen" -#: ../lib/modules/nisMailAliasUser.inc:356 +#: ../lib/modules/nisMailAliasUser.inc:348 msgid "Create new alias" msgstr "Neuen Alias anlegen" @@ -3231,7 +3233,7 @@ msgstr "Erstellzeit" msgid "Cron configuration" msgstr "Cron-Einstellungen" -#: ../lib/modules/windowsUser.inc:2005 ../lib/modules/inetOrgPerson.inc:1699 +#: ../lib/modules/windowsUser.inc:1982 ../lib/modules/inetOrgPerson.inc:1700 msgid "Crop image" msgstr "Bild zuschneiden" @@ -3269,9 +3271,9 @@ msgstr "Eigenes Symbol" msgid "Custom label" msgstr "Eigener Bezeichner" -#: ../lib/modules/customScripts.inc:48 ../lib/modules/customScripts.inc:59 -#: ../lib/modules/customScripts.inc:73 ../lib/modules/customScripts.inc:98 -#: ../lib/modules/customScripts.inc:288 +#: ../lib/modules/customScripts.inc:47 ../lib/modules/customScripts.inc:58 +#: ../lib/modules/customScripts.inc:72 ../lib/modules/customScripts.inc:97 +#: ../lib/modules/customScripts.inc:293 msgid "Custom scripts" msgstr "Eigene Skripte" @@ -3325,7 +3327,7 @@ msgstr "DHCP-Einstellungen" #: ../templates/3rdParty/pla/htdocs/delete_form.php:35 #: ../templates/3rdParty/pla/htdocs/delete_form.php:112 #: ../templates/3rdParty/pla/htdocs/mass_delete.php:57 ../lib/modules.inc:443 -#: ../lib/modules.inc:2098 +#: ../lib/modules.inc:2105 msgid "DN" msgstr "DN" @@ -3340,6 +3342,7 @@ msgid "DN settings" msgstr "DN-Einstellungen" #: ../templates/upload/masscreate.php:334 ../help/help.inc:242 +#: ../help/help.inc:250 msgid "DN suffix" msgstr "DN-Suffix" @@ -3388,12 +3391,12 @@ msgstr "DSML V.1-Export" #: ../lib/modules/asteriskAccount.inc:271 #: ../lib/modules/asteriskAccount.inc:360 #: ../lib/modules/asteriskAccount.inc:625 -#: ../lib/modules/asteriskAccount.inc:1232 +#: ../lib/modules/asteriskAccount.inc:1233 msgid "DTFM flags" msgstr "DTFM-Flags" #: ../lib/modules/asteriskAccount.inc:138 -#: ../lib/modules/asteriskAccount.inc:988 +#: ../lib/modules/asteriskAccount.inc:989 msgid "DTMF mode" msgstr "DTFM-Modus" @@ -3446,9 +3449,9 @@ msgstr "Datenbankbenutzer" msgid "Database user and password" msgstr "Datenbankbenutzer und Passwort" -#: ../lib/modules/pykotaUser.inc:560 ../lib/modules/pykotaUser.inc:611 -#: ../lib/modules/pykotaUser.inc:922 ../lib/modules/pykotaUser.inc:986 -#: ../lib/modules/pykotaUser.inc:1009 +#: ../lib/modules/pykotaUser.inc:561 ../lib/modules/pykotaUser.inc:607 +#: ../lib/modules/pykotaUser.inc:904 ../lib/modules/pykotaUser.inc:968 +#: ../lib/modules/pykotaUser.inc:991 msgid "Date" msgstr "Datum" @@ -3474,11 +3477,11 @@ msgstr "" msgid "Days to delete" msgstr "Tage bis zum Löschen" -#: ../templates/lists/changePassword.php:452 ../lib/types/user.inc:468 +#: ../templates/lists/changePassword.php:452 ../lib/types/user.inc:469 msgid "Deactivate" msgstr "Deaktivieren" -#: ../lib/types/user.inc:337 ../lib/types/user.inc:1124 +#: ../lib/types/user.inc:338 ../lib/types/user.inc:1125 msgid "Deactivated" msgstr "Deaktiviert" @@ -3489,7 +3492,7 @@ msgstr "Debug" #: ../templates/3rdParty/pla/lib/TemplateRender.php:570 #: ../templates/3rdParty/pla/lib/TemplateRender.php:687 #: ../lib/modules/qmailUser.inc:47 ../lib/modules/qmailUser.inc:258 -#: ../lib/modules/qmailUser.inc:997 +#: ../lib/modules/qmailUser.inc:837 msgid "Default" msgstr "Standard" @@ -3503,8 +3506,8 @@ msgstr "Standard (\\r\\n)" msgid "Default gateway" msgstr "Standard-Gateway" -#: ../templates/config/confmain.php:253 -#: ../templates/selfService/adminMain.php:454 ../help/help.inc:103 +#: ../templates/config/confmain.php:257 +#: ../templates/selfService/adminMain.php:460 ../help/help.inc:103 msgid "Default language" msgstr "Standardsprache" @@ -3512,7 +3515,7 @@ msgstr "Standardsprache" msgid "Default method to output a random password." msgstr "Standardmethode zur Ausgabe zufälliger Passwörter." -#: ../templates/config/confmain.php:344 ../help/help.inc:222 +#: ../templates/config/confmain.php:348 ../help/help.inc:222 msgid "Default password output" msgstr "Standardausgabe für Passwörter" @@ -3525,8 +3528,8 @@ msgstr "Standardpasswortrichtlinie" #: ../lib/modules/asteriskAccount.inc:294 #: ../lib/modules/asteriskAccount.inc:528 #: ../lib/modules/asteriskAccount.inc:748 -#: ../lib/modules/asteriskAccount.inc:1011 -#: ../lib/modules/asteriskAccount.inc:1255 +#: ../lib/modules/asteriskAccount.inc:1012 +#: ../lib/modules/asteriskAccount.inc:1256 msgid "Default user" msgstr "Standardbenutzer" @@ -3547,7 +3550,7 @@ msgstr "" "Wenn Sie keine LAM-Module entwickeln verwenden Sie bitte den Standard. Damit " "unterdrücken Sie Meldungen für Entwickler." -#: ../lib/modules/sambaDomain.inc:118 +#: ../lib/modules/sambaDomain.inc:117 msgid "Defines if workstations may change their passwords." msgstr "" "Hier können Sie bestimmen, ob Computer ihre Domänenpasswörter ändern dürfen." @@ -3560,11 +3563,11 @@ msgstr "" "Gibt das Postfach für Message Waiting Indication (MWI) dieses Teilnehmers an." #: ../lib/passwordExpirationJob.inc:478 ../lib/passwordExpirationJob.inc:525 -#: ../help/help.inc:402 +#: ../help/help.inc:408 msgid "Delay" msgstr "Verzögerung" -#: ../help/help.inc:403 +#: ../help/help.inc:409 msgid "Delay this action by a number of days after account expiry." msgstr "Verzögert die Aktion um die angegebene Anzahl Tage nach Accountablauf." @@ -3591,7 +3594,7 @@ msgstr "" "von emails verwendet wird." #: ../templates/tools/serverInfo.php:293 ../templates/tools/serverInfo.php:355 -#: ../templates/tools/multiEdit.php:148 ../templates/tools/ou_edit.php:135 +#: ../templates/tools/multiEdit.php:150 ../templates/tools/ou_edit.php:135 #: ../templates/config/profmanage.php:250 #: ../templates/config/mainmanage.php:370 #: ../templates/selfService/profManage.php:226 @@ -3601,18 +3604,18 @@ msgstr "" #: ../templates/profedit/profilemain.php:228 #: ../templates/3rdParty/pla/htdocs/delete_form.php:33 #: ../templates/3rdParty/pla/htdocs/delete_form.php:124 -#: ../lib/passwordExpirationJob.inc:483 ../lib/modules/pykotaPrinter.inc:280 +#: ../lib/passwordExpirationJob.inc:483 ../lib/modules/pykotaPrinter.inc:277 #: ../lib/modules/nisNetGroupHost.inc:109 -#: ../lib/modules/sambaSamAccount.inc:1573 ../lib/modules/posixAccount.inc:1962 +#: ../lib/modules/sambaSamAccount.inc:1624 ../lib/modules/posixAccount.inc:1929 #: ../lib/modules/zarafaUser.inc:1468 ../lib/modules/zarafaUser.inc:1519 #: ../lib/modules/bindDLZ.inc:1322 ../lib/modules/bindDLZ.inc:1713 -#: ../lib/modules/kolabUser.inc:676 ../lib/modules/sambaGroupMapping.inc:380 -#: ../lib/modules/ldapPublicKey.inc:352 ../lib/modules/nisNetGroupUser.inc:193 -#: ../lib/modules/yubiKeyUser.inc:314 ../lib/modules/nisnetgroup.inc:260 -#: ../lib/modules/inetOrgPerson.inc:2957 ../lib/modules/inetOrgPerson.inc:3078 -#: ../lib/modules/kopanoUser.inc:1469 ../lib/modules/kopanoUser.inc:1520 +#: ../lib/modules/kolabUser.inc:676 ../lib/modules/sambaGroupMapping.inc:386 +#: ../lib/modules/ldapPublicKey.inc:350 ../lib/modules/nisNetGroupUser.inc:193 +#: ../lib/modules/yubiKeyUser.inc:315 ../lib/modules/nisnetgroup.inc:261 +#: ../lib/modules/inetOrgPerson.inc:2958 ../lib/modules/inetOrgPerson.inc:3079 +#: ../lib/modules/kopanoUser.inc:1388 ../lib/modules/kopanoUser.inc:1439 #: ../lib/modules/customFields.inc:2563 ../lib/modules/customFields.inc:4415 -#: ../lib/lists.inc:576 ../lib/modules.inc:1314 +#: ../lib/lists.inc:565 ../lib/modules.inc:1319 msgid "Delete" msgstr "Löschen" @@ -3643,7 +3646,7 @@ msgstr "Alle CA-Zertifikate löschen" msgid "Delete group" msgstr "Gruppe löschen" -#: ../lib/modules/posixAccount.inc:1758 +#: ../lib/modules/posixAccount.inc:1736 msgid "Delete home directory" msgstr "Heimatverzeichnis löschen" @@ -3651,17 +3654,17 @@ msgstr "Heimatverzeichnis löschen" msgid "Delete mailbox" msgstr "Postfach löschen" -#: ../lib/modules/posixGroup.inc:382 ../lib/modules/posixGroup.inc:394 -#: ../lib/modules/posixAccount.inc:1885 ../lib/modules/windowsUser.inc:1834 +#: ../lib/modules/posixGroup.inc:335 ../lib/modules/posixGroup.inc:344 +#: ../lib/modules/posixAccount.inc:1862 ../lib/modules/windowsUser.inc:1818 msgid "Delete non-matching entries" msgstr "Unpassende Einträge löschen" -#: ../templates/tools/ou_edit.php:218 ../help/help.inc:347 +#: ../templates/tools/ou_edit.php:218 ../help/help.inc:353 msgid "Delete organisational unit" msgstr "Organisational Unit löschen" #: ../templates/3rdParty/pla/lib/functions.php:1923 -#: ../lib/modules/windowsUser.inc:1304 ../lib/modules/inetOrgPerson.inc:1597 +#: ../lib/modules/windowsUser.inc:1307 ../lib/modules/inetOrgPerson.inc:1598 msgid "Delete photo" msgstr "Foto löschen" @@ -3732,7 +3735,7 @@ msgid "Delete selected extensions" msgstr "Ausgewählte Erweiterungen löschen" #: ../lib/types/kopanoDynamicGroupType.inc:165 ../lib/types/gon.inc:202 -#: ../lib/types/zarafaDynamicGroupType.inc:179 ../lib/types/group.inc:254 +#: ../lib/types/zarafaDynamicGroupType.inc:179 ../lib/types/group.inc:252 #: ../lib/types/netgroup.inc:175 msgid "Delete selected groups" msgstr "Ausgewählte Gruppen löschen" @@ -3753,7 +3756,7 @@ msgstr "Ausgewählte Richtlinien löschen" msgid "Delete selected printers" msgstr "Ausgewählte Drucker löschen" -#: ../lib/types/gon.inc:209 ../lib/types/group.inc:261 +#: ../lib/types/gon.inc:209 ../lib/types/group.inc:259 msgid "Delete selected roles" msgstr "Ausgewählte Rollen löschen" @@ -3765,7 +3768,7 @@ msgstr "Ausgewählte gemeinsame Ordner löschen" msgid "Delete selected sudo roles" msgstr "Ausgewählte Sudo-Rollen löschen" -#: ../lib/types/user.inc:675 +#: ../lib/types/user.inc:676 msgid "Delete selected users" msgstr "Ausgewählte Benutzer löschen" @@ -3778,7 +3781,7 @@ msgstr "Ausgewählte Ansichten löschen" msgid "Delete successful: %s" msgstr "Löschen war erfolgreich: %s" -#: ../lib/modules/posixAccount.inc:368 ../lib/modules/posixAccount.inc:1763 +#: ../lib/modules/posixAccount.inc:373 ../lib/modules/posixAccount.inc:1741 msgid "Delete sudo rights" msgstr "Sudo-Rechte löschen" @@ -3791,8 +3794,8 @@ msgstr "Diesen Eintrag löschen" msgid "Delete this job" msgstr "Diesen Job löschen" -#: ../lib/modules/nisMailAliasUser.inc:156 -#: ../lib/modules/nisMailAliasUser.inc:193 +#: ../lib/modules/nisMailAliasUser.inc:155 +#: ../lib/modules/nisMailAliasUser.inc:187 #, php-format msgid "Delete whole alias entry which includes %s recipients." msgstr "Kompletten Aliaseintrag mit %s Empfängern löschen." @@ -3818,7 +3821,7 @@ msgstr "Profil gelöscht." msgid "Deletes an IP range." msgstr "Löscht einen IP-Adressbereich." -#: ../lib/modules/posixAccount.inc:369 +#: ../lib/modules/posixAccount.inc:374 msgid "Deletes the user from all existing sudo rights." msgstr "Entfernt den Benutzer aus allen bestehenden Sudo-Rechten." @@ -3834,33 +3837,33 @@ msgid "Deleting objects" msgstr "Lösche Objekte" #: ../lib/modules/qmailUser.inc:221 ../lib/modules/qmailUser.inc:339 -#: ../lib/modules/qmailUser.inc:390 ../lib/modules/qmailUser.inc:623 -#: ../lib/modules/qmailUser.inc:862 ../lib/modules/qmailUser.inc:1122 -#: ../lib/modules/qmailUser.inc:1224 +#: ../lib/modules/qmailUser.inc:390 ../lib/modules/qmailUser.inc:513 +#: ../lib/modules/qmailUser.inc:687 ../lib/modules/qmailUser.inc:962 +#: ../lib/modules/qmailUser.inc:1064 msgid "Deletion date" msgstr "Löschdatum" #: ../lib/modules/autoDelete.inc:65 ../lib/modules/autoDelete.inc:77 #: ../lib/modules/autoDelete.inc:84 ../lib/modules/autoDelete.inc:117 -#: ../lib/modules/autoDelete.inc:152 ../lib/modules/autoDelete.inc:375 -#: ../lib/modules/autoDelete.inc:379 ../lib/baseType.inc:106 +#: ../lib/modules/autoDelete.inc:152 ../lib/modules/autoDelete.inc:385 +#: ../lib/modules/autoDelete.inc:389 ../lib/baseType.inc:106 msgid "Deletion time" msgstr "Löschdatum" -#: ../lib/lists.inc:1249 +#: ../lib/lists.inc:1248 msgid "Deletion was successful." msgstr "Löschen war erfolgreich." #: ../lib/modules/qmailUser.inc:181 ../lib/modules/qmailUser.inc:256 -#: ../lib/modules/qmailUser.inc:360 ../lib/modules/qmailUser.inc:594 -#: ../lib/modules/qmailUser.inc:1115 +#: ../lib/modules/qmailUser.inc:360 ../lib/modules/qmailUser.inc:495 +#: ../lib/modules/qmailUser.inc:955 msgid "Delivery mode" msgstr "Liefermodus" #: ../lib/modules/qmailUser.inc:142 ../lib/modules/qmailUser.inc:217 #: ../lib/modules/qmailUser.inc:331 ../lib/modules/qmailUser.inc:387 -#: ../lib/modules/qmailUser.inc:613 ../lib/modules/qmailUser.inc:1101 -#: ../lib/modules/qmailUser.inc:1223 +#: ../lib/modules/qmailUser.inc:504 ../lib/modules/qmailUser.inc:941 +#: ../lib/modules/qmailUser.inc:1063 msgid "Delivery program" msgstr "Lieferprogramm" @@ -3868,8 +3871,8 @@ msgstr "Lieferprogramm" #: ../lib/modules/asteriskAccount.inc:278 #: ../lib/modules/asteriskAccount.inc:412 #: ../lib/modules/asteriskAccount.inc:672 -#: ../lib/modules/asteriskAccount.inc:995 -#: ../lib/modules/asteriskAccount.inc:1239 ../lib/modules/dhcp_settings.inc:102 +#: ../lib/modules/asteriskAccount.inc:996 +#: ../lib/modules/asteriskAccount.inc:1240 ../lib/modules/dhcp_settings.inc:102 msgid "Deny" msgstr "Verbieten" @@ -3880,22 +3883,22 @@ msgstr "Drucken verbieten" #: ../lib/types/user.inc:97 ../lib/modules/windowsUser.inc:326 #: ../lib/modules/windowsUser.inc:722 ../lib/modules/windowsUser.inc:776 #: ../lib/modules/windowsUser.inc:907 ../lib/modules/windowsUser.inc:954 -#: ../lib/modules/windowsUser.inc:1187 ../lib/modules/windowsUser.inc:2746 -#: ../lib/modules/windowsUser.inc:2938 ../lib/modules/windowsUser.inc:3647 +#: ../lib/modules/windowsUser.inc:1186 ../lib/modules/windowsUser.inc:2718 +#: ../lib/modules/windowsUser.inc:2910 ../lib/modules/windowsUser.inc:3619 #: ../lib/modules/inetOrgPerson.inc:164 ../lib/modules/inetOrgPerson.inc:421 #: ../lib/modules/inetOrgPerson.inc:543 ../lib/modules/inetOrgPerson.inc:741 #: ../lib/modules/inetOrgPerson.inc:745 ../lib/modules/inetOrgPerson.inc:1491 -#: ../lib/modules/inetOrgPerson.inc:1494 ../lib/modules/inetOrgPerson.inc:1977 -#: ../lib/modules/inetOrgPerson.inc:2045 ../lib/modules/inetOrgPerson.inc:2783 -#: ../lib/modules/inetOrgPerson.inc:3878 ../lib/modules/inetOrgPerson.inc:3914 +#: ../lib/modules/inetOrgPerson.inc:1494 ../lib/modules/inetOrgPerson.inc:1978 +#: ../lib/modules/inetOrgPerson.inc:2046 ../lib/modules/inetOrgPerson.inc:2784 +#: ../lib/modules/inetOrgPerson.inc:3879 ../lib/modules/inetOrgPerson.inc:3915 msgid "Department" msgstr "Unternehmensbereich" #: ../lib/modules/windowsUser.inc:330 ../lib/modules/windowsUser.inc:334 #: ../lib/modules/windowsUser.inc:730 ../lib/modules/windowsUser.inc:779 #: ../lib/modules/windowsUser.inc:910 ../lib/modules/windowsUser.inc:955 -#: ../lib/modules/windowsUser.inc:1190 ../lib/modules/windowsUser.inc:2747 -#: ../lib/modules/windowsUser.inc:2939 ../lib/modules/windowsUser.inc:3648 +#: ../lib/modules/windowsUser.inc:1189 ../lib/modules/windowsUser.inc:2719 +#: ../lib/modules/windowsUser.inc:2911 ../lib/modules/windowsUser.inc:3620 msgid "Department number" msgstr "Nummer des Unternehmensbereichs" @@ -3930,15 +3933,15 @@ msgstr "" #: ../lib/types/automountType.inc:91 ../lib/types/user.inc:100 #: ../lib/types/dhcp.inc:99 ../lib/types/netgroup.inc:95 #: ../lib/modules/pykotaPrinter.inc:98 ../lib/modules/pykotaPrinter.inc:152 -#: ../lib/modules/pykotaPrinter.inc:193 ../lib/modules/pykotaPrinter.inc:247 -#: ../lib/modules/pykotaPrinter.inc:580 ../lib/modules/account.inc:78 +#: ../lib/modules/pykotaPrinter.inc:193 ../lib/modules/pykotaPrinter.inc:246 +#: ../lib/modules/pykotaPrinter.inc:572 ../lib/modules/account.inc:78 #: ../lib/modules/account.inc:88 ../lib/modules/account.inc:98 #: ../lib/modules/account.inc:106 ../lib/modules/account.inc:222 -#: ../lib/modules/account.inc:244 ../lib/modules/windowsHost.inc:78 -#: ../lib/modules/windowsHost.inc:101 ../lib/modules/windowsHost.inc:120 -#: ../lib/modules/windowsHost.inc:143 ../lib/modules/windowsHost.inc:285 -#: ../lib/modules/posixGroup.inc:232 ../lib/modules/posixGroup.inc:473 -#: ../lib/modules/posixGroup.inc:701 ../lib/modules/posixGroup.inc:719 +#: ../lib/modules/account.inc:244 ../lib/modules/windowsHost.inc:77 +#: ../lib/modules/windowsHost.inc:100 ../lib/modules/windowsHost.inc:119 +#: ../lib/modules/windowsHost.inc:142 ../lib/modules/windowsHost.inc:288 +#: ../lib/modules/posixGroup.inc:215 ../lib/modules/posixGroup.inc:422 +#: ../lib/modules/posixGroup.inc:650 ../lib/modules/posixGroup.inc:668 #: ../lib/modules/device.inc:84 ../lib/modules/device.inc:109 #: ../lib/modules/device.inc:117 ../lib/modules/device.inc:159 #: ../lib/modules/device.inc:428 ../lib/modules/dhcp_settings.inc:195 @@ -3949,9 +3952,9 @@ msgstr "" #: ../lib/modules/courierMailAlias.inc:126 #: ../lib/modules/courierMailAlias.inc:155 #: ../lib/modules/courierMailAlias.inc:192 ../lib/modules/pykotaGroup.inc:111 -#: ../lib/modules/pykotaGroup.inc:205 ../lib/modules/pykotaGroup.inc:393 -#: ../lib/modules/pykotaGroup.inc:477 ../lib/modules/pykotaGroup.inc:490 -#: ../lib/modules/posixAccount.inc:460 ../lib/modules/bindDLZ.inc:252 +#: ../lib/modules/pykotaGroup.inc:205 ../lib/modules/pykotaGroup.inc:392 +#: ../lib/modules/pykotaGroup.inc:476 ../lib/modules/pykotaGroup.inc:489 +#: ../lib/modules/posixAccount.inc:465 ../lib/modules/bindDLZ.inc:252 #: ../lib/modules/bindDLZ.inc:256 ../lib/modules/bindDLZ.inc:285 #: ../lib/modules/bindDLZ.inc:303 ../lib/modules/bindDLZ.inc:321 #: ../lib/modules/bindDLZ.inc:346 ../lib/modules/bindDLZ.inc:364 @@ -3965,31 +3968,31 @@ msgstr "" #: ../lib/modules/bindDLZ.inc:1919 ../lib/modules/bindDLZ.inc:1938 #: ../lib/modules/bindDLZ.inc:1969 ../lib/modules/windowsUser.inc:141 #: ../lib/modules/windowsUser.inc:454 ../lib/modules/windowsUser.inc:841 -#: ../lib/modules/windowsUser.inc:1130 ../lib/modules/windowsUser.inc:2715 +#: ../lib/modules/windowsUser.inc:1129 ../lib/modules/windowsUser.inc:2687 #: ../lib/modules/nisObject.inc:73 ../lib/modules/nisObject.inc:103 #: ../lib/modules/nisObject.inc:114 ../lib/modules/nisObject.inc:155 #: ../lib/modules/nisObject.inc:208 ../lib/modules/sambaGroupMapping.inc:114 -#: ../lib/modules/sambaGroupMapping.inc:518 ../lib/modules/pykotaUser.inc:123 -#: ../lib/modules/pykotaUser.inc:377 ../lib/modules/pykotaUser.inc:753 -#: ../lib/modules/pykotaUser.inc:893 ../lib/modules/pykotaUser.inc:908 +#: ../lib/modules/sambaGroupMapping.inc:515 ../lib/modules/pykotaUser.inc:123 +#: ../lib/modules/pykotaUser.inc:365 ../lib/modules/pykotaUser.inc:735 +#: ../lib/modules/pykotaUser.inc:875 ../lib/modules/pykotaUser.inc:890 #: ../lib/modules/automount.inc:71 ../lib/modules/automount.inc:94 #: ../lib/modules/automount.inc:104 ../lib/modules/automount.inc:132 -#: ../lib/modules/automount.inc:181 ../lib/modules/nisnetgroup.inc:90 -#: ../lib/modules/nisnetgroup.inc:143 ../lib/modules/nisnetgroup.inc:206 -#: ../lib/modules/nisnetgroup.inc:587 ../lib/modules/inetOrgPerson.inc:165 +#: ../lib/modules/automount.inc:181 ../lib/modules/nisnetgroup.inc:89 +#: ../lib/modules/nisnetgroup.inc:142 ../lib/modules/nisnetgroup.inc:205 +#: ../lib/modules/nisnetgroup.inc:571 ../lib/modules/inetOrgPerson.inc:165 #: ../lib/modules/inetOrgPerson.inc:259 ../lib/modules/inetOrgPerson.inc:480 #: ../lib/modules/inetOrgPerson.inc:566 ../lib/modules/inetOrgPerson.inc:1227 -#: ../lib/modules/inetOrgPerson.inc:1230 ../lib/modules/inetOrgPerson.inc:1945 -#: ../lib/modules/inetOrgPerson.inc:2003 ../lib/modules/inetOrgPerson.inc:2915 -#: ../lib/modules/inetOrgPerson.inc:3858 ../lib/modules/inetOrgPerson.inc:3902 +#: ../lib/modules/inetOrgPerson.inc:1230 ../lib/modules/inetOrgPerson.inc:1946 +#: ../lib/modules/inetOrgPerson.inc:2004 ../lib/modules/inetOrgPerson.inc:2916 +#: ../lib/modules/inetOrgPerson.inc:3859 ../lib/modules/inetOrgPerson.inc:3903 #: ../lib/modules/sudoRole.inc:69 ../lib/modules/sudoRole.inc:152 -#: ../lib/modules/sudoRole.inc:214 ../lib/modules/sudoRole.inc:304 -#: ../lib/modules/sudoRole.inc:746 ../lib/modules/nsview.inc:63 -#: ../lib/modules/nsview.inc:76 ../lib/modules/nsview.inc:96 -#: ../lib/modules/nsview.inc:125 ../lib/modules/nsview.inc:160 -#: ../lib/modules/oracleService.inc:69 ../lib/modules/oracleService.inc:92 -#: ../lib/modules/oracleService.inc:100 ../lib/modules/oracleService.inc:122 -#: ../lib/modules/oracleService.inc:168 +#: ../lib/modules/sudoRole.inc:214 ../lib/modules/sudoRole.inc:298 +#: ../lib/modules/sudoRole.inc:453 ../lib/modules/nsview.inc:62 +#: ../lib/modules/nsview.inc:75 ../lib/modules/nsview.inc:95 +#: ../lib/modules/nsview.inc:124 ../lib/modules/nsview.inc:159 +#: ../lib/modules/oracleService.inc:68 ../lib/modules/oracleService.inc:91 +#: ../lib/modules/oracleService.inc:99 ../lib/modules/oracleService.inc:121 +#: ../lib/modules/oracleService.inc:166 #: ../lib/modules/organizationalRole.inc:93 #: ../lib/modules/organizationalRole.inc:127 #: ../lib/modules/organizationalRole.inc:145 @@ -3997,13 +4000,13 @@ msgstr "" #: ../lib/modules/organizationalRole.inc:481 #: ../lib/modules/organizationalRole.inc:735 ../lib/modules/fixed_ip.inc:115 #: ../lib/modules/fixed_ip.inc:421 ../lib/modules/fixed_ip.inc:764 -#: ../lib/modules/pykotaBillingCode.inc:81 -#: ../lib/modules/pykotaBillingCode.inc:109 -#: ../lib/modules/pykotaBillingCode.inc:116 -#: ../lib/modules/pykotaBillingCode.inc:159 -#: ../lib/modules/pykotaBillingCode.inc:258 ../lib/modules/windowsGroup.inc:123 +#: ../lib/modules/pykotaBillingCode.inc:80 +#: ../lib/modules/pykotaBillingCode.inc:108 +#: ../lib/modules/pykotaBillingCode.inc:115 +#: ../lib/modules/pykotaBillingCode.inc:162 +#: ../lib/modules/pykotaBillingCode.inc:260 ../lib/modules/windowsGroup.inc:123 #: ../lib/modules/windowsGroup.inc:178 ../lib/modules/windowsGroup.inc:253 -#: ../lib/modules/windowsGroup.inc:297 ../lib/modules/windowsGroup.inc:896 +#: ../lib/modules/windowsGroup.inc:297 ../lib/modules/windowsGroup.inc:878 #: ../lib/modules/groupOfNames.inc:92 ../lib/modules/groupOfNames.inc:138 #: ../lib/modules/groupOfNames.inc:174 ../lib/modules/groupOfNames.inc:211 #: ../lib/modules/groupOfNames.inc:734 ../lib/modules/groupOfNames.inc:1047 @@ -4029,7 +4032,7 @@ msgid "Disable IMAP access" msgstr "IMAP deaktivieren" #: ../lib/modules/sambaSamAccount.inc:395 -#: ../lib/modules/sambaSamAccount.inc:1893 +#: ../lib/modules/sambaSamAccount.inc:1940 msgid "Disable LM hashes" msgstr "LM-Hashe deaktivieren" @@ -4041,8 +4044,8 @@ msgstr "LM-Hashe deaktivieren" msgid "Disable POP3 access" msgstr "POP3 deaktivieren" -#: ../templates/config/confmain.php:496 -#: ../templates/selfService/adminMain.php:516 ../help/help.inc:302 +#: ../templates/config/confmain.php:511 +#: ../templates/selfService/adminMain.php:528 ../help/help.inc:306 msgid "Disable certificate check" msgstr "Zertifikatsprüfung deaktivieren" @@ -4050,7 +4053,7 @@ msgstr "Zertifikatsprüfung deaktivieren" msgid "Disable client updates" msgstr "Client-Änderungen deaktivieren" -#: ../lib/modules/posixGroup.inc:527 ../lib/modules/posixGroup.inc:635 +#: ../lib/modules/posixGroup.inc:476 ../lib/modules/posixGroup.inc:584 msgid "Disable membership management" msgstr "Verwaltung von Mitgliedschaften deaktivieren" @@ -4079,7 +4082,7 @@ msgid "Disables the client to update DNS entries." msgstr "" "Deaktiviert die Möglichkeit, dass Clients DNS-Einträge verändern dürfen." -#: ../lib/modules/posixGroup.inc:528 +#: ../lib/modules/posixGroup.inc:477 msgid "Disables the group membership management." msgstr "Deaktiviert die Verwaltung von Gruppenmitgliedschaften." @@ -4097,7 +4100,7 @@ msgstr "" msgid "Disallow TGT-based tickets" msgstr "TGT-basierte Tickets verbieten" -#: ../lib/modules/mitKerberos.inc:173 ../lib/modules/mitKerberos.inc:443 +#: ../lib/modules/mitKerberos.inc:173 ../lib/modules/mitKerberos.inc:449 #: ../lib/modules/heimdalKerberos.inc:147 #: ../lib/modules/heimdalKerberos.inc:375 msgid "Disallow all tickets" @@ -4107,37 +4110,37 @@ msgstr "Alle Tickets verbieten" msgid "Disallow delete" msgstr "Verbiete Löschen" -#: ../lib/modules/mitKerberos.inc:161 ../lib/modules/mitKerberos.inc:413 +#: ../lib/modules/mitKerberos.inc:161 ../lib/modules/mitKerberos.inc:419 #: ../lib/modules/heimdalKerberos.inc:132 #: ../lib/modules/heimdalKerberos.inc:345 msgid "Disallow forwardable tickets" msgstr "Weiterleitbare Tickets verbieten" -#: ../lib/modules/mitKerberos.inc:158 ../lib/modules/mitKerberos.inc:428 +#: ../lib/modules/mitKerberos.inc:158 ../lib/modules/mitKerberos.inc:434 #: ../lib/modules/heimdalKerberos.inc:141 #: ../lib/modules/heimdalKerberos.inc:360 msgid "Disallow post-dated tickets" msgstr "Vordatierte Tickets verbieten" -#: ../lib/modules/mitKerberos.inc:167 ../lib/modules/mitKerberos.inc:418 +#: ../lib/modules/mitKerberos.inc:167 ../lib/modules/mitKerberos.inc:424 #: ../lib/modules/heimdalKerberos.inc:135 #: ../lib/modules/heimdalKerberos.inc:350 msgid "Disallow proxiable tickets" msgstr "Proxy-Tickets verbieten" -#: ../lib/modules/mitKerberos.inc:164 ../lib/modules/mitKerberos.inc:423 +#: ../lib/modules/mitKerberos.inc:164 ../lib/modules/mitKerberos.inc:429 #: ../lib/modules/heimdalKerberos.inc:138 #: ../lib/modules/heimdalKerberos.inc:355 msgid "Disallow renewable tickets" msgstr "Erneuerbare Tickets verbieten" -#: ../lib/modules/mitKerberos.inc:185 ../lib/modules/mitKerberos.inc:433 +#: ../lib/modules/mitKerberos.inc:185 ../lib/modules/mitKerberos.inc:439 #: ../lib/modules/heimdalKerberos.inc:144 #: ../lib/modules/heimdalKerberos.inc:365 msgid "Disallow service tickets" msgstr "Service-Tickets verbieten" -#: ../lib/modules/mitKerberos.inc:170 ../lib/modules/mitKerberos.inc:438 +#: ../lib/modules/mitKerberos.inc:170 ../lib/modules/mitKerberos.inc:444 msgid "Disallow user-to-user authentication" msgstr "Benutzer-zu-Benutzer Authentifizierung verbieten" @@ -4145,23 +4148,23 @@ msgstr "Benutzer-zu-Benutzer Authentifizierung verbieten" #: ../lib/modules/asteriskAccount.inc:286 #: ../lib/modules/asteriskAccount.inc:470 #: ../lib/modules/asteriskAccount.inc:707 -#: ../lib/modules/asteriskAccount.inc:1003 -#: ../lib/modules/asteriskAccount.inc:1247 +#: ../lib/modules/asteriskAccount.inc:1004 +#: ../lib/modules/asteriskAccount.inc:1248 msgid "Disallowed codec" msgstr "Unerlaubter Codec" -#: ../lib/modules/sambaDomain.inc:113 ../lib/modules/sambaDomain.inc:193 -#: ../lib/modules/sambaDomain.inc:299 ../lib/modules/sambaDomain.inc:615 +#: ../lib/modules/sambaDomain.inc:112 ../lib/modules/sambaDomain.inc:192 +#: ../lib/modules/sambaDomain.inc:292 ../lib/modules/sambaDomain.inc:612 msgid "Disconnect users outside logon hours" msgstr "Abmeldung außerhalb der Anmeldezeiten" #: ../lib/modules/sambaSamAccount.inc:239 -#: ../lib/modules/sambaSamAccount.inc:1481 -#: ../lib/modules/sambaSamAccount.inc:1951 +#: ../lib/modules/sambaSamAccount.inc:1542 +#: ../lib/modules/sambaSamAccount.inc:1998 msgid "Disconnection time limit" msgstr "Zeitlimit für Trennung" -#: ../lib/modules/sambaDomain.inc:114 +#: ../lib/modules/sambaDomain.inc:113 msgid "Disconnects users if they are logged in outside logon hours." msgstr "" "Meldet Benutzer ab, die außerhalb der definierten Anmeldezeiten aktiv sind." @@ -4170,7 +4173,7 @@ msgstr "" #: ../lib/modules/zarafaGroup.inc:123 ../lib/modules/zarafaGroup.inc:756 #: ../lib/modules/kopanoGroup.inc:122 ../lib/modules/kopanoGroup.inc:717 #: ../lib/modules/zarafaUser.inc:193 ../lib/modules/zarafaUser.inc:1812 -#: ../lib/modules/kopanoUser.inc:193 ../lib/modules/kopanoUser.inc:1823 +#: ../lib/modules/kopanoUser.inc:193 ../lib/modules/kopanoUser.inc:1742 #: ../lib/modules/organizationalRole.inc:110 #: ../lib/modules/organizationalRole.inc:727 #: ../lib/modules/groupOfNames.inc:121 ../lib/modules/groupOfNames.inc:1037 @@ -4181,26 +4184,26 @@ msgstr "Anzeigeformat" msgid "Display multiple groups as accordion" msgstr "Mehrere Gruppen als Akkordeon anzeigen" -#: ../templates/config/confmain.php:223 ../lib/types/group.inc:111 +#: ../templates/config/confmain.php:227 ../lib/types/group.inc:111 #: ../lib/types/user.inc:98 ../lib/modules/sambaSamAccount.inc:111 #: ../lib/modules/sambaSamAccount.inc:210 #: ../lib/modules/sambaSamAccount.inc:260 #: ../lib/modules/sambaSamAccount.inc:427 -#: ../lib/modules/sambaSamAccount.inc:1089 -#: ../lib/modules/sambaSamAccount.inc:1699 -#: ../lib/modules/sambaSamAccount.inc:1922 ../lib/modules/windowsUser.inc:145 +#: ../lib/modules/sambaSamAccount.inc:1121 +#: ../lib/modules/sambaSamAccount.inc:1746 +#: ../lib/modules/sambaSamAccount.inc:1969 ../lib/modules/windowsUser.inc:145 #: ../lib/modules/windowsUser.inc:442 ../lib/modules/windowsUser.inc:762 #: ../lib/modules/windowsUser.inc:842 ../lib/modules/windowsUser.inc:992 -#: ../lib/modules/windowsUser.inc:1128 ../lib/modules/windowsUser.inc:2716 +#: ../lib/modules/windowsUser.inc:1127 ../lib/modules/windowsUser.inc:2688 #: ../lib/modules/sambaGroupMapping.inc:112 #: ../lib/modules/sambaGroupMapping.inc:146 -#: ../lib/modules/sambaGroupMapping.inc:334 -#: ../lib/modules/sambaGroupMapping.inc:516 ../help/help.inc:200 +#: ../lib/modules/sambaGroupMapping.inc:336 +#: ../lib/modules/sambaGroupMapping.inc:513 ../help/help.inc:200 msgid "Display name" msgstr "Anzeigename" #: ../templates/lists/changePassword.php:316 -#: ../templates/config/confmain.php:340 +#: ../templates/config/confmain.php:344 msgid "Display on screen" msgstr "Auf dem Bildschirm anzeigen" @@ -4224,12 +4227,12 @@ msgstr "Angezeigte Attribute" msgid "Distribution" msgstr "Versand" -#: ../lib/modules/posixAccount.inc:397 ../lib/modules/posixAccount.inc:2337 +#: ../lib/modules/posixAccount.inc:402 ../lib/modules/posixAccount.inc:2295 msgid "Do not add object class" msgstr "Objektklasse nicht hinzufügen" #: ../lib/modules/passwordSelfReset.inc:143 -#: ../lib/modules/passwordSelfReset.inc:669 +#: ../lib/modules/passwordSelfReset.inc:667 msgid "Do not ask security question" msgstr "Keine Sicherheitsfrage stellen" @@ -4261,18 +4264,18 @@ msgstr "Wollen Sie diesen Eintrag erstellen?" msgid "Do you want to make these changes?" msgstr "Wollen Sie die Änderungen übernehmen?" -#: ../lib/modules/sambaSamAccount.inc:212 +#: ../templates/config/confmain.php:506 ../lib/modules/sambaSamAccount.inc:212 #: ../lib/modules/sambaSamAccount.inc:338 #: ../lib/modules/sambaSamAccount.inc:420 #: ../lib/modules/sambaSamAccount.inc:544 -#: ../lib/modules/sambaSamAccount.inc:1225 -#: ../lib/modules/sambaSamAccount.inc:1744 -#: ../lib/modules/sambaSamAccount.inc:1766 -#: ../lib/modules/sambaSamAccount.inc:1928 ../lib/modules/windowsUser.inc:133 -#: ../lib/modules/windowsUser.inc:2803 ../lib/modules/sambaGroupMapping.inc:158 -#: ../lib/modules/sambaGroupMapping.inc:361 -#: ../lib/modules/sambaGroupMapping.inc:550 ../lib/modules/nisnetgroup.inc:230 -#: ../lib/modules/nisnetgroup.inc:594 +#: ../lib/modules/sambaSamAccount.inc:1272 +#: ../lib/modules/sambaSamAccount.inc:1791 +#: ../lib/modules/sambaSamAccount.inc:1813 +#: ../lib/modules/sambaSamAccount.inc:1975 ../lib/modules/windowsUser.inc:133 +#: ../lib/modules/windowsUser.inc:2775 ../lib/modules/sambaGroupMapping.inc:158 +#: ../lib/modules/sambaGroupMapping.inc:367 +#: ../lib/modules/sambaGroupMapping.inc:547 ../lib/modules/nisnetgroup.inc:226 +#: ../lib/modules/nisnetgroup.inc:578 ../help/help.inc:332 msgid "Domain" msgstr "Domäne" @@ -4280,10 +4283,10 @@ msgstr "Domäne" msgid "Domain KRBTGT" msgstr "Domäne KRBTGT" -#: ../lib/types/smbDomain.inc:94 ../lib/modules/sambaDomain.inc:81 -#: ../lib/modules/sambaDomain.inc:151 ../lib/modules/sambaDomain.inc:185 -#: ../lib/modules/sambaDomain.inc:256 ../lib/modules/sambaDomain.inc:261 -#: ../lib/modules/sambaDomain.inc:594 +#: ../lib/types/smbDomain.inc:94 ../lib/modules/sambaDomain.inc:80 +#: ../lib/modules/sambaDomain.inc:150 ../lib/modules/sambaDomain.inc:184 +#: ../lib/modules/sambaDomain.inc:248 ../lib/modules/sambaDomain.inc:251 +#: ../lib/modules/sambaDomain.inc:591 msgid "Domain SID" msgstr "Domänen-SID" @@ -4303,7 +4306,7 @@ msgstr "Domänenadministratoren" msgid "Domain certificate admins" msgstr "Domänen-Zertifikats-Administratoren" -#: ../lib/modules/sambaSamAccount.inc:80 ../lib/modules/sambaSamAccount.inc:758 +#: ../lib/modules/sambaSamAccount.inc:80 ../lib/modules/sambaSamAccount.inc:768 #: ../lib/modules/sambaGroupMapping.inc:63 msgid "Domain computers" msgstr "Domänen-PCs" @@ -4345,15 +4348,15 @@ msgstr "Domänenlokal" #: ../lib/modules/nisNetGroupHost.inc:261 ../lib/modules/nisNetGroupUser.inc:98 #: ../lib/modules/nisNetGroupUser.inc:186 #: ../lib/modules/nisNetGroupUser.inc:435 -#: ../lib/modules/nisNetGroupUser.inc:479 ../lib/modules/nisnetgroup.inc:157 -#: ../lib/modules/sambaDomain.inc:77 ../lib/modules/sambaDomain.inc:144 -#: ../lib/modules/sambaDomain.inc:184 ../lib/modules/sambaDomain.inc:241 -#: ../lib/modules/sambaDomain.inc:246 ../lib/modules/sambaDomain.inc:593 +#: ../lib/modules/nisNetGroupUser.inc:479 ../lib/modules/nisnetgroup.inc:156 +#: ../lib/modules/sambaDomain.inc:76 ../lib/modules/sambaDomain.inc:143 +#: ../lib/modules/sambaDomain.inc:183 ../lib/modules/sambaDomain.inc:236 +#: ../lib/modules/sambaDomain.inc:239 ../lib/modules/sambaDomain.inc:590 msgid "Domain name" msgstr "Domänenname" -#: ../lib/modules/nisNetGroupUser.inc:98 ../lib/modules/nisnetgroup.inc:157 -#: ../lib/modules/sambaDomain.inc:208 ../lib/modules/sambaDomain.inc:209 +#: ../lib/modules/nisNetGroupUser.inc:98 ../lib/modules/nisnetgroup.inc:156 +#: ../lib/modules/sambaDomain.inc:207 ../lib/modules/sambaDomain.inc:208 msgid "Domain name is invalid!" msgstr "Domänenname ist ungültig!" @@ -4368,17 +4371,17 @@ msgid "Domain schema admins" msgstr "Domänen-Schema-Administratoren" #: ../lib/modules/sambaSamAccount.inc:407 -#: ../lib/modules/sambaSamAccount.inc:2269 +#: ../lib/modules/sambaSamAccount.inc:2316 msgid "Domain suffix" msgstr "Domain-Suffix" #: ../lib/modules/sambaSamAccount.inc:79 -#: ../lib/modules/sambaSamAccount.inc:2187 +#: ../lib/modules/sambaSamAccount.inc:2234 #: ../lib/modules/sambaGroupMapping.inc:61 msgid "Domain users" msgstr "Domänenbenutzer" -#: ../lib/modules/windowsUser.inc:259 ../lib/modules/windowsUser.inc:3624 +#: ../lib/modules/windowsUser.inc:259 ../lib/modules/windowsUser.inc:3596 msgid "Domains" msgstr "Domänen" @@ -4387,7 +4390,7 @@ msgstr "Domänen" msgid "Don't stop on errors" msgstr "Bei Fehlern weitermachen" -#: ../lib/modules/windowsUser.inc:2017 ../lib/modules/inetOrgPerson.inc:1710 +#: ../lib/modules/windowsUser.inc:1993 ../lib/modules/inetOrgPerson.inc:1711 msgid "Done" msgstr "Fertig" @@ -4404,11 +4407,11 @@ msgstr "CA-Zertifikate herunterladen" msgid "Download sample CSV file" msgstr "Beispiel-CSV-Datei runterladen" -#: ../templates/tools/multiEdit.php:186 +#: ../templates/tools/multiEdit.php:188 msgid "Dry run" msgstr "Trockentest" -#: ../templates/tools/multiEdit.php:462 +#: ../templates/tools/multiEdit.php:464 msgid "Dry run finished." msgstr "Trockentest beendet." @@ -4438,13 +4441,13 @@ msgstr "" #: ../templates/pdfedit/pdfmain.php:244 #: ../templates/profedit/profilemain.php:224 -#: ../lib/modules/sambaSamAccount.inc:1230 -#: ../lib/modules/sambaSamAccount.inc:1235 ../lib/modules/windowsGroup.inc:379 -#: ../lib/modules/windowsGroup.inc:406 ../lib/lists.inc:570 +#: ../lib/modules/sambaSamAccount.inc:1278 +#: ../lib/modules/sambaSamAccount.inc:1286 ../lib/modules/windowsGroup.inc:378 +#: ../lib/modules/windowsGroup.inc:407 ../lib/lists.inc:559 msgid "Edit" msgstr "Editieren" -#: ../lib/modules.inc:1432 +#: ../lib/modules.inc:1438 msgid "Edit again" msgstr "Erneut editieren" @@ -4452,11 +4455,11 @@ msgstr "Erneut editieren" msgid "Edit general settings" msgstr "Allgemeine Einstellungen ändern" -#: ../lib/modules/posixAccount.inc:1659 ../lib/modules/windowsUser.inc:1311 +#: ../lib/modules/posixAccount.inc:1643 ../lib/modules/windowsUser.inc:1313 msgid "Edit groups" msgstr "Gruppen ändern" -#: ../lib/modules/posixGroup.inc:255 +#: ../lib/modules/posixGroup.inc:240 msgid "Edit members" msgstr "Mitglieder ändern" @@ -4472,7 +4475,7 @@ msgstr "Serverprofile ändern" msgid "Edit subgroups" msgstr "Untergruppen ändern" -#: ../lib/modules/sambaSamAccount.inc:1186 +#: ../lib/modules/sambaSamAccount.inc:1229 msgid "Edit workstations" msgstr "Arbeitsstationen ändern" @@ -4484,7 +4487,7 @@ msgstr "Arbeitsstationen ändern" #: ../lib/modules/zarafaGroup.inc:217 ../lib/modules/kopanoGroup.inc:209 #: ../lib/modules/kopanoGroup.inc:212 ../lib/modules/kopanoDynamicGroup.inc:210 #: ../lib/modules/kopanoDynamicGroup.inc:211 -#: ../lib/modules/passwordSelfReset.inc:630 ../lib/modules/nisMailAlias.inc:343 +#: ../lib/modules/passwordSelfReset.inc:628 ../lib/modules/nisMailAlias.inc:349 msgid "Email" msgstr "EMail" @@ -4508,9 +4511,9 @@ msgstr "EMail-Adresse \"%s\" wird bereits verwendet." #: ../lib/modules/zarafaDynamicGroup.inc:165 #: ../lib/modules/zarafaDynamicGroup.inc:184 #: ../lib/modules/zarafaDynamicGroup.inc:411 ../lib/modules/qmailUser.inc:165 -#: ../lib/modules/qmailUser.inc:402 ../lib/modules/qmailUser.inc:457 -#: ../lib/modules/qmailUser.inc:949 ../lib/modules/qmailUser.inc:1079 -#: ../lib/modules/qmailUser.inc:1090 ../lib/modules/zarafaGroup.inc:76 +#: ../lib/modules/qmailUser.inc:402 ../lib/modules/qmailUser.inc:448 +#: ../lib/modules/qmailUser.inc:789 ../lib/modules/qmailUser.inc:919 +#: ../lib/modules/qmailUser.inc:930 ../lib/modules/zarafaGroup.inc:76 #: ../lib/modules/zarafaGroup.inc:77 ../lib/modules/zarafaGroup.inc:189 #: ../lib/modules/zarafaGroup.inc:527 ../lib/modules/zarafaGroup.inc:639 #: ../lib/modules/zarafaGroup.inc:650 ../lib/modules/courierMailAlias.inc:77 @@ -4524,11 +4527,11 @@ msgstr "EMail-Adresse \"%s\" wird bereits verwendet." #: ../lib/modules/kopanoGroup.inc:601 ../lib/modules/windowsUser.inc:161 #: ../lib/modules/windowsUser.inc:496 ../lib/modules/windowsUser.inc:763 #: ../lib/modules/windowsUser.inc:846 ../lib/modules/windowsUser.inc:1014 -#: ../lib/modules/windowsUser.inc:1016 ../lib/modules/windowsUser.inc:1141 -#: ../lib/modules/windowsUser.inc:2720 ../lib/modules/pykotaUser.inc:119 -#: ../lib/modules/pykotaUser.inc:282 ../lib/modules/pykotaUser.inc:373 -#: ../lib/modules/pykotaUser.inc:745 ../lib/modules/pykotaUser.inc:890 -#: ../lib/modules/pykotaUser.inc:906 ../lib/modules/kopanoDynamicGroup.inc:66 +#: ../lib/modules/windowsUser.inc:1016 ../lib/modules/windowsUser.inc:1140 +#: ../lib/modules/windowsUser.inc:2692 ../lib/modules/pykotaUser.inc:119 +#: ../lib/modules/pykotaUser.inc:282 ../lib/modules/pykotaUser.inc:361 +#: ../lib/modules/pykotaUser.inc:727 ../lib/modules/pykotaUser.inc:872 +#: ../lib/modules/pykotaUser.inc:888 ../lib/modules/kopanoDynamicGroup.inc:66 #: ../lib/modules/kopanoDynamicGroup.inc:67 #: ../lib/modules/kopanoDynamicGroup.inc:127 #: ../lib/modules/kopanoDynamicGroup.inc:169 @@ -4538,9 +4541,9 @@ msgstr "EMail-Adresse \"%s\" wird bereits verwendet." #: ../lib/modules/inetOrgPerson.inc:159 ../lib/modules/inetOrgPerson.inc:387 #: ../lib/modules/inetOrgPerson.inc:525 ../lib/modules/inetOrgPerson.inc:662 #: ../lib/modules/inetOrgPerson.inc:666 ../lib/modules/inetOrgPerson.inc:1427 -#: ../lib/modules/inetOrgPerson.inc:1430 ../lib/modules/inetOrgPerson.inc:1961 -#: ../lib/modules/inetOrgPerson.inc:2039 ../lib/modules/inetOrgPerson.inc:2570 -#: ../lib/modules/inetOrgPerson.inc:3873 ../lib/modules/inetOrgPerson.inc:3910 +#: ../lib/modules/inetOrgPerson.inc:1430 ../lib/modules/inetOrgPerson.inc:1962 +#: ../lib/modules/inetOrgPerson.inc:2040 ../lib/modules/inetOrgPerson.inc:2571 +#: ../lib/modules/inetOrgPerson.inc:3874 ../lib/modules/inetOrgPerson.inc:3911 #: ../lib/modules/kolabSharedFolder.inc:94 #: ../lib/modules/kolabSharedFolder.inc:151 #: ../lib/modules/kolabSharedFolder.inc:202 @@ -4554,13 +4557,13 @@ msgstr "EMail-Adresse \"%s\" wird bereits verwendet." #: ../lib/modules/asteriskVoicemail.inc:372 ../lib/modules/qmailGroup.inc:88 #: ../lib/modules/qmailGroup.inc:92 ../lib/modules/qmailGroup.inc:205 #: ../lib/modules/qmailGroup.inc:336 ../lib/modules/qmailGroup.inc:385 -#: ../lib/modules/qmailGroup.inc:422 ../lib/modules/qmailGroup.inc:973 -#: ../lib/modules/imapAccess.inc:235 ../lib/modules/passwordSelfReset.inc:1034 -#: ../lib/modules/passwordSelfReset.inc:1259 +#: ../lib/modules/qmailGroup.inc:414 ../lib/modules/qmailGroup.inc:824 +#: ../lib/modules/imapAccess.inc:235 ../lib/modules/passwordSelfReset.inc:1032 +#: ../lib/modules/passwordSelfReset.inc:1257 #: ../lib/modules/windowsGroup.inc:131 ../lib/modules/windowsGroup.inc:214 #: ../lib/modules/windowsGroup.inc:261 ../lib/modules/windowsGroup.inc:281 -#: ../lib/modules/windowsGroup.inc:299 ../lib/modules/windowsGroup.inc:898 -#: ../lib/modules/windowsGroup.inc:1100 +#: ../lib/modules/windowsGroup.inc:299 ../lib/modules/windowsGroup.inc:880 +#: ../lib/modules/windowsGroup.inc:1081 msgid "Email address" msgstr "EMail-Adresse" @@ -4589,8 +4592,8 @@ msgstr "Diese Email-Adressen moderieren diese Liste (z.B. Emails genehmigen)." #: ../lib/modules/windowsUser.inc:251 ../lib/modules/windowsUser.inc:502 #: ../lib/modules/windowsUser.inc:764 ../lib/modules/windowsUser.inc:847 #: ../lib/modules/windowsUser.inc:1018 ../lib/modules/windowsUser.inc:1020 -#: ../lib/modules/windowsUser.inc:1142 ../lib/modules/windowsUser.inc:2721 -#: ../lib/modules/windowsUser.inc:2968 ../lib/modules/kolabUser.inc:127 +#: ../lib/modules/windowsUser.inc:1141 ../lib/modules/windowsUser.inc:2693 +#: ../lib/modules/windowsUser.inc:2940 ../lib/modules/kolabUser.inc:127 #: ../lib/modules/kopanoUser.inc:142 ../lib/modules/kolabSharedFolder.inc:124 msgid "Email alias" msgstr "EMail-Alias" @@ -4650,7 +4653,7 @@ msgstr "EMail-Aliasliste hat ungültiges Format!" #: ../lib/modules/zarafaUser.inc:1276 ../lib/modules/zarafaUser.inc:1530 #: ../lib/modules/zarafaUser.inc:1833 ../lib/modules/kopanoContact.inc:134 #: ../lib/modules/kopanoContact.inc:167 ../lib/modules/kopanoContact.inc:221 -#: ../lib/modules/kopanoContact.inc:591 ../lib/modules/kolabUser.inc:173 +#: ../lib/modules/kopanoContact.inc:588 ../lib/modules/kolabUser.inc:173 #: ../lib/modules/kolabUser.inc:200 ../lib/modules/kolabUser.inc:299 #: ../lib/modules/kolabUser.inc:616 ../lib/modules/kopanoDynamicGroup.inc:70 #: ../lib/modules/kopanoDynamicGroup.inc:74 @@ -4660,8 +4663,8 @@ msgstr "EMail-Aliasliste hat ungültiges Format!" #: ../lib/modules/kopanoDynamicGroup.inc:213 #: ../lib/modules/kopanoDynamicGroup.inc:354 ../lib/modules/kopanoUser.inc:210 #: ../lib/modules/kopanoUser.inc:396 ../lib/modules/kopanoUser.inc:411 -#: ../lib/modules/kopanoUser.inc:468 ../lib/modules/kopanoUser.inc:1276 -#: ../lib/modules/kopanoUser.inc:1531 ../lib/modules/kopanoUser.inc:1844 +#: ../lib/modules/kopanoUser.inc:468 ../lib/modules/kopanoUser.inc:1195 +#: ../lib/modules/kopanoUser.inc:1450 ../lib/modules/kopanoUser.inc:1763 #: ../lib/modules/kolabSharedFolder.inc:184 #: ../lib/modules/kolabSharedFolder.inc:200 #: ../lib/modules/kolabSharedFolder.inc:259 @@ -4675,27 +4678,27 @@ msgstr "Emailformat" #: ../lib/types/user.inc:99 ../lib/modules/windowsUser.inc:310 #: ../lib/modules/windowsUser.inc:698 ../lib/modules/windowsUser.inc:898 -#: ../lib/modules/windowsUser.inc:1175 ../lib/modules/windowsUser.inc:2743 -#: ../lib/modules/windowsUser.inc:3643 ../lib/modules/inetOrgPerson.inc:275 +#: ../lib/modules/windowsUser.inc:1174 ../lib/modules/windowsUser.inc:2715 +#: ../lib/modules/windowsUser.inc:3615 ../lib/modules/inetOrgPerson.inc:275 #: ../lib/modules/inetOrgPerson.inc:555 ../lib/modules/inetOrgPerson.inc:774 #: ../lib/modules/inetOrgPerson.inc:1467 ../lib/modules/inetOrgPerson.inc:1470 -#: ../lib/modules/inetOrgPerson.inc:1972 ../lib/modules/inetOrgPerson.inc:3882 -#: ../lib/modules/inetOrgPerson.inc:3916 -#: ../lib/modules/passwordSelfReset.inc:633 -#: ../lib/modules/passwordSelfReset.inc:1044 -#: ../lib/modules/passwordSelfReset.inc:1263 +#: ../lib/modules/inetOrgPerson.inc:1973 ../lib/modules/inetOrgPerson.inc:3883 +#: ../lib/modules/inetOrgPerson.inc:3917 +#: ../lib/modules/passwordSelfReset.inc:631 +#: ../lib/modules/passwordSelfReset.inc:1042 +#: ../lib/modules/passwordSelfReset.inc:1261 msgid "Employee number" msgstr "Personalnummer" #: ../lib/modules/windowsUser.inc:314 ../lib/modules/windowsUser.inc:706 #: ../lib/modules/windowsUser.inc:791 ../lib/modules/windowsUser.inc:901 -#: ../lib/modules/windowsUser.inc:1037 ../lib/modules/windowsUser.inc:1178 -#: ../lib/modules/windowsUser.inc:2744 ../lib/modules/windowsUser.inc:3644 +#: ../lib/modules/windowsUser.inc:1037 ../lib/modules/windowsUser.inc:1177 +#: ../lib/modules/windowsUser.inc:2716 ../lib/modules/windowsUser.inc:3616 #: ../lib/modules/inetOrgPerson.inc:96 ../lib/modules/inetOrgPerson.inc:283 #: ../lib/modules/inetOrgPerson.inc:537 ../lib/modules/inetOrgPerson.inc:586 #: ../lib/modules/inetOrgPerson.inc:1475 ../lib/modules/inetOrgPerson.inc:1478 -#: ../lib/modules/inetOrgPerson.inc:1970 ../lib/modules/inetOrgPerson.inc:2057 -#: ../lib/modules/inetOrgPerson.inc:3876 ../lib/modules/inetOrgPerson.inc:3912 +#: ../lib/modules/inetOrgPerson.inc:1971 ../lib/modules/inetOrgPerson.inc:2058 +#: ../lib/modules/inetOrgPerson.inc:3877 ../lib/modules/inetOrgPerson.inc:3913 msgid "Employee type" msgstr "Angestelltentyp" @@ -4709,7 +4712,7 @@ msgid "Empty password submitted. Please try again." msgstr "Das Passwort war leer. Bitte erneut eingeben." #: ../lib/modules/passwordSelfReset.inc:99 -#: ../lib/modules/passwordSelfReset.inc:621 +#: ../lib/modules/passwordSelfReset.inc:619 msgid "Enable password self reset link" msgstr "Link für Passwortrücksetzung aktivieren" @@ -4743,7 +4746,7 @@ msgstr "" "serverseitige Sortierung wählen (wechseln Sie die Sortierung hier falls " "Passwörter nicht aus der Historie gelöscht werden)." -#: ../lib/modules/ppolicy.inc:97 +#: ../lib/modules/ppolicy.inc:96 msgid "" "Enables quality checking (e.g. password length) of passwords. If set to " "\"force\" then you need to disable password hashing in your LAM server " @@ -4754,7 +4757,7 @@ msgstr "" "von LAM im Serverprofil deaktivieren um weiterhin Passwörter ändern zu " "können." -#: ../templates/tools/multiEdit.php:337 +#: ../templates/tools/multiEdit.php:339 #: ../templates/3rdParty/pla/lib/export_functions.php:160 msgid "Encountered an error while performing search." msgstr "Die LDAP-Suche ist fehlgeschlagen." @@ -4791,11 +4794,11 @@ msgstr "Ende" msgid "End of line" msgstr "Zeilenende" -#: ../lib/modules/puppetClient.inc:127 ../lib/modules/puppetClient.inc:525 +#: ../lib/modules/puppetClient.inc:127 ../lib/modules/puppetClient.inc:521 msgid "Enforce classes" msgstr "Klassen erzwingen" -#: ../templates/selfService/adminMain.php:457 ../help/help.inc:107 +#: ../templates/selfService/adminMain.php:463 ../help/help.inc:107 msgid "Enforce language" msgstr "Sprache erzwingen" @@ -4803,13 +4806,13 @@ msgstr "Sprache erzwingen" msgid "Enforce password change on next login by default." msgstr "Standardmäßig Passwortwechsel beim nächsten Login erzwingen." -#: ../lib/modules/posixAccount.inc:402 ../lib/modules/windowsUser.inc:403 +#: ../lib/modules/posixAccount.inc:407 ../lib/modules/windowsUser.inc:403 msgid "Enter one group per line that should be ignored when syncing groups." msgstr "" "Bitte geben Sie eine Gruppe pro Zeile ein. Diese werden dann bei der " "Gruppensynchronisierung ignoriert." -#: ../lib/modules/posixAccount.inc:406 ../lib/modules/groupOfNamesUser.inc:69 +#: ../lib/modules/posixAccount.inc:411 ../lib/modules/groupOfNamesUser.inc:69 msgid "" "Enter the base DN of your groups here. This is only required if you want to " "display memberships on the self service page." @@ -4879,14 +4882,14 @@ msgstr "Eintrag erstellt" #: ../templates/3rdParty/pla/htdocs/mass_edit.php:27 #: ../templates/3rdParty/pla/htdocs/rdelete.php:24 #: ../templates/3rdParty/pla/htdocs/rdelete.php:56 -#: ../templates/3rdParty/pla/htdocs/mass_delete.php:33 ../lib/account.inc:942 +#: ../templates/3rdParty/pla/htdocs/mass_delete.php:33 ../lib/account.inc:954 msgid "Entry does not exist" msgstr "Eintrag existiert nicht" #: ../lib/modules/puppetClient.inc:87 ../lib/modules/puppetClient.inc:91 #: ../lib/modules/puppetClient.inc:139 ../lib/modules/puppetClient.inc:165 #: ../lib/modules/puppetClient.inc:173 ../lib/modules/puppetClient.inc:253 -#: ../lib/modules/puppetClient.inc:403 +#: ../lib/modules/puppetClient.inc:399 msgid "Environment" msgstr "Umgebung" @@ -4895,7 +4898,7 @@ msgid "Equality" msgstr "Gleichheit" #: ../lib/modules/zarafaUser.inc:559 ../lib/modules/zarafaUser.inc:1290 -#: ../lib/modules/kopanoUser.inc:559 ../lib/modules/kopanoUser.inc:1290 +#: ../lib/modules/kopanoUser.inc:528 ../lib/modules/kopanoUser.inc:1209 msgid "Equipment" msgstr "Equipment" @@ -4919,7 +4922,7 @@ msgstr "Fehler" msgid "Error number" msgstr "Fehlernummer" -#: ../lib/modules/mitKerberos.inc:1177 ../lib/modules/heimdalKerberos.inc:1045 +#: ../lib/modules/mitKerberos.inc:1199 ../lib/modules/heimdalKerberos.inc:1045 msgid "Error while changing Kerberos password." msgstr "Konnte Kerberos-Passwort nicht ändern." @@ -4928,7 +4931,7 @@ msgstr "Konnte Kerberos-Passwort nicht ändern." msgid "Error while deleting DN: %s" msgstr "Fehler beim Löschen von DN: %s" -#: ../templates/login.php:217 ../templates/selfService/selfServiceLogin.php:250 +#: ../templates/login.php:217 ../templates/selfService/selfServiceLogin.php:251 #: ../lib/env.inc:245 msgid "Evaluation Licence" msgstr "Testlizenz" @@ -4941,12 +4944,12 @@ msgstr "" "Jeder Accounttyp benötigt exakt ein Basismodul. Dieses Modul stellt die " "strukturelle Objektklasse bereit." -#: ../lib/modules/customScripts.inc:67 ../lib/modules/customScripts.inc:80 +#: ../lib/modules/customScripts.inc:66 ../lib/modules/customScripts.inc:79 #: ../lib/modules/selfRegistration.inc:81 ../lib/modules/zarafaContact.inc:100 #: ../lib/modules/zarafaGroup.inc:106 ../lib/modules/kopanoGroup.inc:105 #: ../lib/modules/zarafaUser.inc:164 ../lib/modules/kopanoContact.inc:100 #: ../lib/modules/kopanoUser.inc:164 ../help/help.inc:78 ../help/help.inc:84 -#: ../help/help.inc:94 ../help/help.inc:99 ../help/help.inc:123 +#: ../help/help.inc:94 ../help/help.inc:99 msgid "Example" msgstr "Beispiel" @@ -4954,16 +4957,16 @@ msgstr "Beispiel" msgid "Example value" msgstr "Beispielwert" -#: ../help/help.inc:66 +#: ../help/help.inc:66 ../help/help.inc:123 msgid "Examples" msgstr "Beispiele" -#: ../lib/modules/posixAccount.inc:401 ../lib/modules/posixAccount.inc:2264 -#: ../lib/modules/windowsUser.inc:402 ../lib/modules/windowsUser.inc:3669 +#: ../lib/modules/posixAccount.inc:406 ../lib/modules/posixAccount.inc:2222 +#: ../lib/modules/windowsUser.inc:402 ../lib/modules/windowsUser.inc:3641 msgid "Exclude from group sync" msgstr "Von der Gruppensynchronisierung ausnehmen" -#: ../templates/config/confmain.php:299 +#: ../templates/config/confmain.php:303 msgid "Execute" msgstr "Ausführen" @@ -4992,8 +4995,8 @@ msgstr "Ablaufzeit" #: ../lib/modules/asteriskAccount.inc:289 #: ../lib/modules/asteriskAccount.inc:492 #: ../lib/modules/asteriskAccount.inc:719 -#: ../lib/modules/asteriskAccount.inc:1006 -#: ../lib/modules/asteriskAccount.inc:1250 +#: ../lib/modules/asteriskAccount.inc:1007 +#: ../lib/modules/asteriskAccount.inc:1251 msgid "Expiration timestamp" msgstr "Ablaufdatum" @@ -5001,13 +5004,13 @@ msgstr "Ablaufdatum" msgid "Expiration timestamp (\"regseconds\" option)." msgstr "Ablaufzeit (\"regseconds\" Option)." -#: ../lib/types/ppolicyType.inc:89 ../lib/modules/ppolicy.inc:116 -#: ../lib/modules/ppolicy.inc:148 ../lib/modules/ppolicy.inc:174 -#: ../lib/modules/ppolicy.inc:212 ../lib/modules/ppolicy.inc:478 +#: ../lib/types/ppolicyType.inc:89 ../lib/modules/ppolicy.inc:115 +#: ../lib/modules/ppolicy.inc:147 ../lib/modules/ppolicy.inc:173 +#: ../lib/modules/ppolicy.inc:205 ../lib/modules/ppolicy.inc:471 msgid "Expire warning" msgstr "Ablaufwarnung" -#: ../lib/types/user.inc:384 ../lib/types/user.inc:892 +#: ../lib/types/user.inc:385 ../lib/types/user.inc:893 msgid "Expired" msgstr "Abgelaufen" @@ -5029,7 +5032,7 @@ msgid "Export" msgstr "Export" #: ../templates/pdfedit/pdfmain.php:264 ../templates/pdfedit/pdfmain.php:265 -#: ../help/help.inc:264 +#: ../help/help.inc:266 msgid "Export PDF structure" msgstr "PDF-Struktur exportieren" @@ -5068,7 +5071,7 @@ msgstr "Anzahl Erweiterungen: %s" #: ../lib/modules/asteriskExtension.inc:146 #: ../lib/modules/asteriskExtension.inc:288 #: ../lib/modules/asteriskExtension.inc:292 -#: ../lib/modules/asteriskExtension.inc:789 +#: ../lib/modules/asteriskExtension.inc:787 msgid "Extension name" msgstr "Erweiterungsname" @@ -5076,8 +5079,8 @@ msgstr "Erweiterungsname" #: ../lib/modules/asteriskExtension.inc:139 #: ../lib/modules/asteriskExtension.inc:160 #: ../lib/modules/asteriskExtension.inc:234 -#: ../lib/modules/asteriskExtension.inc:460 -#: ../lib/modules/asteriskExtension.inc:790 +#: ../lib/modules/asteriskExtension.inc:458 +#: ../lib/modules/asteriskExtension.inc:788 msgid "Extension owners" msgstr "Besitzer der Erweiterung" @@ -5096,7 +5099,7 @@ msgstr "Fehlgeschlagen" #: ../lib/modules/mitKerberos.inc:127 ../lib/modules/mitKerberos.inc:251 #: ../lib/modules/mitKerberos.inc:289 ../lib/modules/mitKerberos.inc:336 -#: ../lib/modules/mitKerberos.inc:785 +#: ../lib/modules/mitKerberos.inc:807 msgid "Failed logins" msgstr "Fehlgeschlagene Anmeldungen" @@ -5114,23 +5117,23 @@ msgstr "Konnte Eintrag nicht erstellen!" msgid "Failover peer" msgstr "Ausfallknoten" -#: ../lib/types/ppolicyType.inc:88 ../lib/modules/ppolicy.inc:112 -#: ../lib/modules/ppolicy.inc:153 ../lib/modules/ppolicy.inc:173 -#: ../lib/modules/ppolicy.inc:276 ../lib/modules/ppolicy.inc:477 +#: ../lib/types/ppolicyType.inc:88 ../lib/modules/ppolicy.inc:111 +#: ../lib/modules/ppolicy.inc:152 ../lib/modules/ppolicy.inc:172 +#: ../lib/modules/ppolicy.inc:269 ../lib/modules/ppolicy.inc:470 msgid "Failure count interval" msgstr "Fehlerzählerintervall" #: ../lib/modules/windowsUser.inc:201 ../lib/modules/windowsUser.inc:609 #: ../lib/modules/windowsUser.inc:874 ../lib/modules/windowsUser.inc:944 -#: ../lib/modules/windowsUser.inc:1000 ../lib/modules/windowsUser.inc:1161 -#: ../lib/modules/windowsUser.inc:2730 ../lib/modules/windowsUser.inc:2928 -#: ../lib/modules/windowsUser.inc:3630 ../lib/modules/inetOrgPerson.inc:78 +#: ../lib/modules/windowsUser.inc:1000 ../lib/modules/windowsUser.inc:1160 +#: ../lib/modules/windowsUser.inc:2702 ../lib/modules/windowsUser.inc:2900 +#: ../lib/modules/windowsUser.inc:3602 ../lib/modules/inetOrgPerson.inc:78 #: ../lib/modules/inetOrgPerson.inc:160 ../lib/modules/inetOrgPerson.inc:371 #: ../lib/modules/inetOrgPerson.inc:519 ../lib/modules/inetOrgPerson.inc:646 #: ../lib/modules/inetOrgPerson.inc:650 ../lib/modules/inetOrgPerson.inc:1411 -#: ../lib/modules/inetOrgPerson.inc:1414 ../lib/modules/inetOrgPerson.inc:1963 -#: ../lib/modules/inetOrgPerson.inc:2036 ../lib/modules/inetOrgPerson.inc:2625 -#: ../lib/modules/inetOrgPerson.inc:3871 ../lib/modules/inetOrgPerson.inc:3909 +#: ../lib/modules/inetOrgPerson.inc:1414 ../lib/modules/inetOrgPerson.inc:1964 +#: ../lib/modules/inetOrgPerson.inc:2037 ../lib/modules/inetOrgPerson.inc:2626 +#: ../lib/modules/inetOrgPerson.inc:3872 ../lib/modules/inetOrgPerson.inc:3910 msgid "Fax number" msgstr "Faxnummer" @@ -5138,8 +5141,8 @@ msgstr "Faxnummer" #: ../lib/modules/zarafaUser.inc:670 ../lib/modules/zarafaUser.inc:1329 #: ../lib/modules/zarafaUser.inc:1368 ../lib/modules/zarafaUser.inc:1832 #: ../lib/modules/kopanoUser.inc:173 ../lib/modules/kopanoUser.inc:402 -#: ../lib/modules/kopanoUser.inc:670 ../lib/modules/kopanoUser.inc:1329 -#: ../lib/modules/kopanoUser.inc:1368 ../lib/modules/kopanoUser.inc:1843 +#: ../lib/modules/kopanoUser.inc:631 ../lib/modules/kopanoUser.inc:1248 +#: ../lib/modules/kopanoUser.inc:1287 ../lib/modules/kopanoUser.inc:1762 msgid "Features" msgstr "Funktionen" @@ -5187,9 +5190,9 @@ msgstr "" "Login ausgeführt wird. $user und $group werden mit Benutzer- und Gruppenname " "ersetzt." -#: ../lib/modules/kopanoServer.inc:76 ../lib/modules/kopanoServer.inc:112 -#: ../lib/modules/kopanoServer.inc:137 ../lib/modules/kopanoServer.inc:154 -#: ../lib/modules/kopanoServer.inc:196 ../lib/modules/kopanoServer.inc:329 +#: ../lib/modules/kopanoServer.inc:75 ../lib/modules/kopanoServer.inc:111 +#: ../lib/modules/kopanoServer.inc:136 ../lib/modules/kopanoServer.inc:153 +#: ../lib/modules/kopanoServer.inc:177 ../lib/modules/kopanoServer.inc:309 #: ../lib/modules/zarafaServer.inc:76 ../lib/modules/zarafaServer.inc:112 #: ../lib/modules/zarafaServer.inc:137 ../lib/modules/zarafaServer.inc:154 #: ../lib/modules/zarafaServer.inc:196 ../lib/modules/zarafaServer.inc:329 @@ -5198,7 +5201,7 @@ msgstr "Dateipfad" #: ../templates/upload/masscreate.php:272 ../lib/tools/fileUpload.inc:45 #: ../lib/modules/ldapPublicKey.inc:93 ../lib/modules/customFields.inc:4238 -#: ../lib/lists.inc:885 +#: ../lib/lists.inc:875 msgid "File upload" msgstr "Dateiupload" @@ -5207,14 +5210,14 @@ msgstr "Dateiupload" #: ../lib/types/zarafaAddressListType.inc:97 #: ../lib/types/zarafaDynamicGroupType.inc:98 #: ../lib/types/kopanoAddressListType.inc:83 -#: ../lib/modules/pykotaPrinter.inc:127 ../lib/modules/pykotaPrinter.inc:424 +#: ../lib/modules/pykotaPrinter.inc:127 ../lib/modules/pykotaPrinter.inc:411 #: ../lib/modules/zarafaDynamicGroup.inc:90 #: ../lib/modules/zarafaDynamicGroup.inc:157 #: ../lib/modules/zarafaDynamicGroup.inc:170 #: ../lib/modules/zarafaDynamicGroup.inc:223 #: ../lib/modules/zarafaDynamicGroup.inc:413 -#: ../lib/modules/zarafaContact.inc:94 ../lib/modules/posixGroup.inc:364 -#: ../lib/modules/posixGroup.inc:522 ../lib/modules/nisNetGroupHost.inc:119 +#: ../lib/modules/zarafaContact.inc:94 ../lib/modules/posixGroup.inc:471 +#: ../lib/modules/nisNetGroupHost.inc:119 #: ../lib/modules/zarafaAddressList.inc:78 #: ../lib/modules/zarafaAddressList.inc:133 #: ../lib/modules/zarafaAddressList.inc:144 @@ -5227,23 +5230,23 @@ msgstr "Dateiupload" #: ../lib/modules/kopanoDynamicGroup.inc:161 #: ../lib/modules/kopanoDynamicGroup.inc:174 #: ../lib/modules/kopanoDynamicGroup.inc:208 -#: ../lib/modules/kopanoDynamicGroup.inc:353 ../lib/modules/nisnetgroup.inc:107 -#: ../lib/modules/nisnetgroup.inc:490 ../lib/modules/kopanoUser.inc:158 +#: ../lib/modules/kopanoDynamicGroup.inc:353 ../lib/modules/nisnetgroup.inc:106 +#: ../lib/modules/nisnetgroup.inc:477 ../lib/modules/kopanoUser.inc:158 #: ../lib/modules/kopanoAddressList.inc:78 #: ../lib/modules/kopanoAddressList.inc:133 #: ../lib/modules/kopanoAddressList.inc:144 #: ../lib/modules/kopanoAddressList.inc:173 -#: ../lib/modules/kopanoAddressList.inc:293 ../lib/modules/nsview.inc:62 -#: ../lib/modules/nsview.inc:72 ../lib/modules/nsview.inc:90 -#: ../lib/modules/nsview.inc:109 ../lib/modules/nsview.inc:123 -#: ../lib/modules/nsview.inc:159 ../lib/modules/organizationalRole.inc:105 +#: ../lib/modules/kopanoAddressList.inc:293 ../lib/modules/nsview.inc:61 +#: ../lib/modules/nsview.inc:71 ../lib/modules/nsview.inc:89 +#: ../lib/modules/nsview.inc:108 ../lib/modules/nsview.inc:122 +#: ../lib/modules/nsview.inc:158 ../lib/modules/organizationalRole.inc:105 #: ../lib/modules/organizationalRole.inc:269 #: ../lib/modules/organizationalRole.inc:360 -#: ../lib/modules/windowsGroup.inc:646 ../lib/modules/groupOfNames.inc:116 +#: ../lib/modules/windowsGroup.inc:622 ../lib/modules/groupOfNames.inc:116 #: ../lib/modules/groupOfNames.inc:316 ../lib/modules/groupOfNames.inc:391 #: ../lib/modules/groupOfNames.inc:465 ../lib/modules/groupOfNames.inc:541 -#: ../lib/modules/nisMailAlias.inc:92 ../lib/modules/nisMailAlias.inc:372 -#: ../lib/lists.inc:468 ../lib/baseModule.inc:1604 ../lib/baseModule.inc:1630 +#: ../lib/modules/nisMailAlias.inc:92 ../lib/modules/nisMailAlias.inc:374 +#: ../lib/lists.inc:456 ../lib/baseModule.inc:1609 ../lib/baseModule.inc:1636 #: ../help/help.inc:178 msgid "Filter" msgstr "Filtern" @@ -5252,24 +5255,24 @@ msgstr "Filtern" msgid "Filter performed" msgstr "Angewandter Filter" -#: ../lib/modules/asteriskExtension.inc:453 +#: ../lib/modules/asteriskExtension.inc:451 msgid "Filters" msgstr "Filter" -#: ../templates/tools/multiEdit.php:563 +#: ../templates/tools/multiEdit.php:561 msgid "Finished all operations." msgstr "Alle Operationen beendet." #: ../lib/types/user.inc:102 ../lib/modules/selfRegistration.inc:82 #: ../lib/modules/selfRegistration.inc:205 ../lib/modules/windowsUser.inc:149 #: ../lib/modules/windowsUser.inc:424 ../lib/modules/windowsUser.inc:843 -#: ../lib/modules/windowsUser.inc:994 ../lib/modules/windowsUser.inc:1125 -#: ../lib/modules/windowsUser.inc:2717 ../lib/modules/inetOrgPerson.inc:68 +#: ../lib/modules/windowsUser.inc:994 ../lib/modules/windowsUser.inc:1124 +#: ../lib/modules/windowsUser.inc:2689 ../lib/modules/inetOrgPerson.inc:68 #: ../lib/modules/inetOrgPerson.inc:158 ../lib/modules/inetOrgPerson.inc:236 #: ../lib/modules/inetOrgPerson.inc:476 ../lib/modules/inetOrgPerson.inc:578 #: ../lib/modules/inetOrgPerson.inc:1194 ../lib/modules/inetOrgPerson.inc:1197 -#: ../lib/modules/inetOrgPerson.inc:1947 ../lib/modules/inetOrgPerson.inc:2548 -#: ../lib/modules/inetOrgPerson.inc:3903 +#: ../lib/modules/inetOrgPerson.inc:1948 ../lib/modules/inetOrgPerson.inc:2549 +#: ../lib/modules/inetOrgPerson.inc:3904 msgid "First name" msgstr "Vorname" @@ -5287,11 +5290,11 @@ msgstr "" msgid "Fix IP addresses" msgstr "Feste IP-Adressen" -#: ../templates/config/confmain.php:420 +#: ../templates/config/confmain.php:424 msgid "Fixed list" msgstr "Feste Liste" -#: ../lib/modules/posixGroup.inc:599 ../lib/modules/posixAccount.inc:2183 +#: ../lib/modules/posixGroup.inc:548 ../lib/modules/posixAccount.inc:2141 msgid "Fixed range" msgstr "Fester Adressbereich" @@ -5299,13 +5302,13 @@ msgstr "Fester Adressbereich" msgid "Folding marks" msgstr "Faltmarken" -#: ../templates/config/confmain.php:226 -#: ../templates/selfService/adminMain.php:424 ../help/help.inc:89 +#: ../templates/config/confmain.php:230 +#: ../templates/selfService/adminMain.php:430 ../help/help.inc:89 msgid "Follow referrals" msgstr "Referrals folgen" -#: ../templates/upload/masscreate.php:307 ../lib/lists.inc:791 -#: ../help/help.inc:268 +#: ../templates/upload/masscreate.php:307 ../lib/lists.inc:780 +#: ../help/help.inc:270 msgid "Font" msgstr "Schriftart" @@ -5313,7 +5316,7 @@ msgstr "Schriftart" msgid "For automatic invitation handling." msgstr "Regelt automatische Einladungen." -#: ../lib/modules/nisnetgroup.inc:104 +#: ../lib/modules/nisnetgroup.inc:103 msgid "" "For the upload please specify the entries in the format \"(HOST,USER," "DOMAIN)\". Multiple entries are separated by semicolon." @@ -5321,33 +5324,33 @@ msgstr "" "Für den Upload geben Sie bitte die Einträge im Format \"(HOST,BENUTZER," "DOMAIN)\" ein. Mehrere Einträge werden durch Strichpunkt getrennt." -#: ../templates/lists/changePassword.php:238 ../lib/modules/locking389ds.inc:85 -#: ../lib/modules/mitKerberos.inc:182 ../lib/modules/mitKerberos.inc:408 -#: ../lib/modules/shadowAccount.inc:374 ../lib/modules/heimdalKerberos.inc:126 -#: ../lib/modules/heimdalKerberos.inc:340 ../lib/modules.inc:1131 -#: ../help/help.inc:258 +#: ../templates/lists/changePassword.php:238 ../lib/modules/locking389ds.inc:87 +#: ../lib/modules/mitKerberos.inc:182 ../lib/modules/mitKerberos.inc:414 +#: ../lib/modules/shadowAccount.inc:376 ../lib/modules/heimdalKerberos.inc:126 +#: ../lib/modules/heimdalKerberos.inc:340 ../lib/modules.inc:1132 +#: ../help/help.inc:260 msgid "Force password change" msgstr "Passwortänderung erzwingen" -#: ../templates/config/confmain.php:337 ../help/help.inc:224 +#: ../templates/config/confmain.php:341 ../help/help.inc:224 msgid "Force password change by default" msgstr "Passwortwechsel standardmäßig erzwingen" -#: ../lib/modules/posixGroup.inc:535 ../lib/modules/posixGroup.inc:582 +#: ../lib/modules/posixGroup.inc:484 ../lib/modules/posixGroup.inc:531 msgid "Force sync with group of names" msgstr "Sync mit Namensgruppe erzwingen" -#: ../lib/modules/passwordSelfReset.inc:962 +#: ../lib/modules/passwordSelfReset.inc:960 msgid "Forgot password?" msgstr "Passwort vergessen?" -#: ../templates/3rdParty/pla/lib/QueryRender.php:457 ../help/help.inc:334 +#: ../templates/3rdParty/pla/lib/QueryRender.php:457 ../help/help.inc:340 msgid "Format" msgstr "Format" #: ../lib/modules/qmailUser.inc:173 ../lib/modules/qmailUser.inc:243 #: ../lib/modules/qmailUser.inc:358 ../lib/modules/qmailUser.inc:406 -#: ../lib/modules/qmailUser.inc:511 ../lib/modules/qmailUser.inc:1092 +#: ../lib/modules/qmailUser.inc:453 ../lib/modules/qmailUser.inc:932 msgid "Forwarding address" msgstr "Weiterleitungsadresse" @@ -5366,21 +5369,21 @@ msgstr "Freies Drucken" msgid "FreeRadius" msgstr "FreeRadius" -#: ../lib/modules/sambaSamAccount.inc:1337 +#: ../lib/modules/sambaSamAccount.inc:1387 msgid "Friday" msgstr "Freitag" -#: ../templates/config/confmain.php:353 ../lib/passwordExpirationJob.inc:83 +#: ../templates/config/confmain.php:357 ../lib/passwordExpirationJob.inc:83 #: ../lib/passwordExpirationJob.inc:122 ../lib/modules/selfRegistration.inc:89 #: ../lib/modules/selfRegistration.inc:217 #: ../lib/modules/passwordSelfReset.inc:119 -#: ../lib/modules/passwordSelfReset.inc:665 -#: ../lib/modules/passwordSelfReset.inc:681 ../help/help.inc:328 -#: ../help/help.inc:372 +#: ../lib/modules/passwordSelfReset.inc:663 +#: ../lib/modules/passwordSelfReset.inc:679 ../help/help.inc:334 +#: ../help/help.inc:378 msgid "From address" msgstr "Absenderadresse" -#: ../templates/config/confmain.php:600 +#: ../templates/config/confmain.php:615 msgid "From address for password mails is invalid." msgstr "Absenderadresse für Passwortmails ist ungültig." @@ -5388,8 +5391,8 @@ msgstr "Absenderadresse für Passwortmails ist ungültig." #: ../lib/modules/asteriskAccount.inc:273 #: ../lib/modules/asteriskAccount.inc:375 #: ../lib/modules/asteriskAccount.inc:634 -#: ../lib/modules/asteriskAccount.inc:990 -#: ../lib/modules/asteriskAccount.inc:1234 +#: ../lib/modules/asteriskAccount.inc:991 +#: ../lib/modules/asteriskAccount.inc:1235 msgid "From domain" msgstr "Quelldomäne" @@ -5401,8 +5404,8 @@ msgstr "Quelldomäne für diesen Account." #: ../lib/modules/asteriskAccount.inc:272 #: ../lib/modules/asteriskAccount.inc:368 #: ../lib/modules/asteriskAccount.inc:630 -#: ../lib/modules/asteriskAccount.inc:989 -#: ../lib/modules/asteriskAccount.inc:1233 +#: ../lib/modules/asteriskAccount.inc:990 +#: ../lib/modules/asteriskAccount.inc:1234 msgid "From user" msgstr "Quellbenutzer" @@ -5414,8 +5417,8 @@ msgstr "Quellbenutzer für diesen Account." #: ../lib/modules/asteriskAccount.inc:274 #: ../lib/modules/asteriskAccount.inc:382 #: ../lib/modules/asteriskAccount.inc:638 -#: ../lib/modules/asteriskAccount.inc:991 -#: ../lib/modules/asteriskAccount.inc:1235 +#: ../lib/modules/asteriskAccount.inc:992 +#: ../lib/modules/asteriskAccount.inc:1236 msgid "Full contact" msgstr "Vollständiger Kontakt" @@ -5441,26 +5444,26 @@ msgstr "Funktion" msgid "GB-12345" msgstr "DE-12345" -#: ../lib/modules/posixGroup.inc:509 ../lib/modules/posixGroup.inc:604 +#: ../lib/modules/posixGroup.inc:458 ../lib/modules/posixGroup.inc:553 msgid "GID generator" msgstr "GID-Generator" #: ../lib/types/host.inc:100 ../lib/types/group.inc:112 #: ../lib/types/user.inc:101 ../lib/modules/qmailUser.inc:201 #: ../lib/modules/qmailUser.inc:298 ../lib/modules/qmailUser.inc:375 -#: ../lib/modules/qmailUser.inc:546 ../lib/modules/qmailUser.inc:1094 -#: ../lib/modules/qmailUser.inc:1219 ../lib/modules/posixGroup.inc:222 -#: ../lib/modules/posixGroup.inc:449 ../lib/modules/posixGroup.inc:455 -#: ../lib/modules/posixGroup.inc:469 ../lib/modules/posixGroup.inc:489 -#: ../lib/modules/posixGroup.inc:718 ../lib/modules/posixGroup.inc:752 -#: ../lib/modules/posixAccount.inc:115 ../lib/modules/posixAccount.inc:297 -#: ../lib/modules/posixAccount.inc:2149 +#: ../lib/modules/qmailUser.inc:461 ../lib/modules/qmailUser.inc:934 +#: ../lib/modules/qmailUser.inc:1059 ../lib/modules/posixGroup.inc:211 +#: ../lib/modules/posixGroup.inc:398 ../lib/modules/posixGroup.inc:404 +#: ../lib/modules/posixGroup.inc:418 ../lib/modules/posixGroup.inc:438 +#: ../lib/modules/posixGroup.inc:667 ../lib/modules/posixGroup.inc:701 +#: ../lib/modules/posixAccount.inc:115 ../lib/modules/posixAccount.inc:302 +#: ../lib/modules/posixAccount.inc:2107 #: ../lib/modules/sambaGroupMapping.inc:110 -#: ../lib/modules/sambaGroupMapping.inc:514 +#: ../lib/modules/sambaGroupMapping.inc:511 msgid "GID number" msgstr "GID Nummer" -#: ../lib/modules/posixGroup.inc:752 +#: ../lib/modules/posixGroup.inc:701 msgid "" "GID number has changed. Please select checkbox to change GID number of users " "and hosts." @@ -5478,7 +5481,7 @@ msgstr "" "ausführen, um die Dateirechte zu ändern: 'find / -gid %s -uid %s -exec chgrp " "%s {} \\;'" -#: ../lib/modules/posixGroup.inc:759 +#: ../lib/modules/posixGroup.inc:708 msgid "GID number has to be a numeric value!" msgstr "GID-Nummer muss eine Zahl sein!" @@ -5486,15 +5489,15 @@ msgstr "GID-Nummer muss eine Zahl sein!" msgid "GID number is already in use." msgstr "GID-Nummer wird bereits verwendet." -#: ../lib/modules/posixAccount.inc:414 ../lib/modules/posixAccount.inc:452 -#: ../lib/modules/posixAccount.inc:1645 ../lib/modules/posixAccount.inc:2122 -#: ../lib/modules/posixAccount.inc:2152 ../lib/modules/posixAccount.inc:2255 -#: ../lib/modules/posixAccount.inc:2324 ../lib/modules/posixAccount.inc:2465 -#: ../lib/modules/posixAccount.inc:2473 +#: ../lib/modules/posixAccount.inc:419 ../lib/modules/posixAccount.inc:457 +#: ../lib/modules/posixAccount.inc:1628 ../lib/modules/posixAccount.inc:2080 +#: ../lib/modules/posixAccount.inc:2110 ../lib/modules/posixAccount.inc:2213 +#: ../lib/modules/posixAccount.inc:2282 ../lib/modules/posixAccount.inc:2423 +#: ../lib/modules/posixAccount.inc:2431 msgid "Gecos" msgstr "Gecos" -#: ../lib/modules/windowsUser.inc:1098 +#: ../lib/modules/windowsUser.inc:1097 msgid "General" msgstr "Allgemein" @@ -5503,7 +5506,7 @@ msgid "General information" msgstr "Allgemeine Informationen" #: ../templates/config/mainmanage.php:295 -#: ../templates/selfService/adminMain.php:401 +#: ../templates/selfService/adminMain.php:407 #: ../templates/profedit/profilepage.php:189 ../lib/configPages.inc:87 msgid "General settings" msgstr "Allgemeine Einstellungen" @@ -5512,7 +5515,7 @@ msgstr "Allgemeine Einstellungen" msgid "Generate new serial number." msgstr "Neue Seriennummer generieren." -#: ../templates/lists/changePassword.php:307 ../help/help.inc:310 +#: ../templates/lists/changePassword.php:307 ../help/help.inc:314 msgid "Generate random password" msgstr "Zufälliges Passwort generieren" @@ -5541,17 +5544,17 @@ msgstr "Zurück" msgid "Go to" msgstr "Gehe zu" -#: ../templates/selfService/adminMain.php:531 ../help/help.inc:312 +#: ../templates/selfService/adminMain.php:543 ../help/help.inc:316 msgid "Google reCAPTCHA" msgstr "Google reCAPTCHA" -#: ../lib/types/ppolicyType.inc:85 ../lib/modules/ppolicy.inc:100 -#: ../lib/modules/ppolicy.inc:149 ../lib/modules/ppolicy.inc:170 -#: ../lib/modules/ppolicy.inc:219 ../lib/modules/ppolicy.inc:474 +#: ../lib/types/ppolicyType.inc:85 ../lib/modules/ppolicy.inc:99 +#: ../lib/modules/ppolicy.inc:148 ../lib/modules/ppolicy.inc:169 +#: ../lib/modules/ppolicy.inc:212 ../lib/modules/ppolicy.inc:467 msgid "Grace authentication limit" msgstr "Aufschublimit" -#: ../lib/modules/quota.inc:129 ../lib/modules/quota.inc:489 +#: ../lib/modules/quota.inc:129 ../lib/modules/quota.inc:467 msgid "Grace block period" msgstr "Block-Frist" @@ -5568,13 +5571,13 @@ msgid "" msgstr "" "Inode-Frist. Viele Dateisysteme verwenden ein festes Maximum von 7 Tagen." -#: ../lib/modules/quota.inc:151 ../lib/modules/quota.inc:493 +#: ../lib/modules/quota.inc:151 ../lib/modules/quota.inc:468 msgid "Grace inode period" msgstr "Inode-Frist" -#: ../templates/config/confmain.php:304 -#: ../templates/selfService/adminMain.php:681 -#: ../templates/selfService/adminMain.php:732 +#: ../templates/config/confmain.php:308 +#: ../templates/selfService/adminMain.php:693 +#: ../templates/selfService/adminMain.php:744 #: ../lib/modules/nisNetGroupHost.inc:100 #: ../lib/modules/nisNetGroupHost.inc:217 #: ../lib/modules/nisNetGroupHost.inc:259 @@ -5584,7 +5587,7 @@ msgstr "Inode-Frist" msgid "Group" msgstr "Gruppe" -#: ../lib/modules/posixAccount.inc:187 ../lib/modules/posixAccount.inc:405 +#: ../lib/modules/posixAccount.inc:187 ../lib/modules/posixAccount.inc:410 #: ../lib/modules/groupOfNamesUser.inc:68 #: ../lib/modules/groupOfNamesUser.inc:85 msgid "Group DN" @@ -5595,14 +5598,14 @@ msgid "Group accounts (e.g. Unix and Samba)" msgstr "Gruppenaccounts (z.B. Unix und Samba)" #: ../lib/types/kopanoDynamicGroupType.inc:162 ../lib/types/gon.inc:199 -#: ../lib/types/zarafaDynamicGroupType.inc:176 ../lib/types/group.inc:251 +#: ../lib/types/zarafaDynamicGroupType.inc:176 ../lib/types/group.inc:249 #: ../lib/types/netgroup.inc:172 #, php-format msgid "Group count: %s" msgstr "Anzahl Gruppen: %s" #: ../lib/types/group.inc:110 ../lib/modules/posixGroup.inc:76 -#: ../lib/modules/nisnetgroup.inc:123 +#: ../lib/modules/nisnetgroup.inc:122 msgid "Group description" msgstr "Gruppenbeschreibung" @@ -5610,7 +5613,7 @@ msgstr "Gruppenbeschreibung" msgid "Group description." msgstr "Gruppenbeschreibung." -#: ../lib/modules/posixGroup.inc:474 +#: ../lib/modules/posixGroup.inc:423 msgid "Group description. If left empty group name will be used." msgstr "Gruppenbeschreibung. Falls leer wird der Gruppename verwendet." @@ -5622,16 +5625,16 @@ msgstr "DNs der Gruppenmitglieder" #: ../templates/3rdParty/pla/htdocs/modify_member_form.php:94 #: ../lib/types/group.inc:114 ../lib/modules/pykotaPrinter.inc:118 #: ../lib/modules/pykotaPrinter.inc:122 ../lib/modules/pykotaPrinter.inc:185 -#: ../lib/modules/pykotaPrinter.inc:198 ../lib/modules/pykotaPrinter.inc:264 -#: ../lib/modules/pykotaPrinter.inc:602 ../lib/modules/posixGroup.inc:85 -#: ../lib/modules/posixGroup.inc:253 ../lib/modules/posixGroup.inc:322 -#: ../lib/modules/posixGroup.inc:477 ../lib/modules/posixGroup.inc:481 -#: ../lib/modules/posixGroup.inc:704 ../lib/modules/posixGroup.inc:716 -#: ../lib/modules/posixGroup.inc:729 ../lib/modules/windowsGroup.inc:377 +#: ../lib/modules/pykotaPrinter.inc:198 ../lib/modules/pykotaPrinter.inc:262 +#: ../lib/modules/pykotaPrinter.inc:594 ../lib/modules/posixGroup.inc:85 +#: ../lib/modules/posixGroup.inc:237 ../lib/modules/posixGroup.inc:304 +#: ../lib/modules/posixGroup.inc:426 ../lib/modules/posixGroup.inc:430 +#: ../lib/modules/posixGroup.inc:653 ../lib/modules/posixGroup.inc:665 +#: ../lib/modules/posixGroup.inc:678 ../lib/modules/windowsGroup.inc:376 msgid "Group members" msgstr "Gruppenmitglieder" -#: ../lib/modules/posixGroup.inc:705 +#: ../lib/modules/posixGroup.inc:654 msgid "Group members (incl. primary members)" msgstr "Gruppenmitglieder (inkl. primären Mitgliedern)" @@ -5644,10 +5647,10 @@ msgstr "Gruppenmitglieder (inkl. primären Mitgliedern)" #: ../lib/modules/zarafaDynamicGroup.inc:180 #: ../lib/modules/zarafaDynamicGroup.inc:204 #: ../lib/modules/zarafaDynamicGroup.inc:410 ../lib/modules/posixGroup.inc:66 -#: ../lib/modules/posixGroup.inc:212 ../lib/modules/posixGroup.inc:497 -#: ../lib/modules/posixGroup.inc:700 ../lib/modules/posixGroup.inc:717 -#: ../lib/modules/posixGroup.inc:760 ../lib/modules/posixGroup.inc:761 -#: ../lib/modules/posixGroup.inc:762 ../lib/modules/pykotaGroup.inc:168 +#: ../lib/modules/posixGroup.inc:208 ../lib/modules/posixGroup.inc:446 +#: ../lib/modules/posixGroup.inc:649 ../lib/modules/posixGroup.inc:666 +#: ../lib/modules/posixGroup.inc:709 ../lib/modules/posixGroup.inc:710 +#: ../lib/modules/posixGroup.inc:711 ../lib/modules/pykotaGroup.inc:168 #: ../lib/modules/pykotaGroup.inc:170 ../lib/modules/pykotaGroup.inc:190 #: ../lib/modules/kopanoDynamicGroup.inc:62 #: ../lib/modules/kopanoDynamicGroup.inc:119 @@ -5655,16 +5658,16 @@ msgstr "Gruppenmitglieder (inkl. primären Mitgliedern)" #: ../lib/modules/kopanoDynamicGroup.inc:183 #: ../lib/modules/kopanoDynamicGroup.inc:184 #: ../lib/modules/kopanoDynamicGroup.inc:204 -#: ../lib/modules/kopanoDynamicGroup.inc:350 ../lib/modules/nisnetgroup.inc:86 -#: ../lib/modules/nisnetgroup.inc:115 ../lib/modules/nisnetgroup.inc:142 -#: ../lib/modules/nisnetgroup.inc:154 ../lib/modules/nisnetgroup.inc:198 -#: ../lib/modules/nisnetgroup.inc:586 ../lib/modules/organizationalRole.inc:89 +#: ../lib/modules/kopanoDynamicGroup.inc:350 ../lib/modules/nisnetgroup.inc:85 +#: ../lib/modules/nisnetgroup.inc:114 ../lib/modules/nisnetgroup.inc:141 +#: ../lib/modules/nisnetgroup.inc:153 ../lib/modules/nisnetgroup.inc:197 +#: ../lib/modules/nisnetgroup.inc:570 ../lib/modules/organizationalRole.inc:89 #: ../lib/modules/organizationalRole.inc:118 #: ../lib/modules/organizationalRole.inc:141 #: ../lib/modules/organizationalRole.inc:480 #: ../lib/modules/windowsGroup.inc:119 ../lib/modules/windowsGroup.inc:171 #: ../lib/modules/windowsGroup.inc:252 ../lib/modules/windowsGroup.inc:279 -#: ../lib/modules/windowsGroup.inc:296 ../lib/modules/windowsGroup.inc:895 +#: ../lib/modules/windowsGroup.inc:296 ../lib/modules/windowsGroup.inc:877 #: ../lib/modules/groupOfNames.inc:88 ../lib/modules/groupOfNames.inc:129 #: ../lib/modules/groupOfNames.inc:170 ../lib/modules/groupOfNames.inc:733 msgid "Group name" @@ -5681,11 +5684,11 @@ msgid "Group name already in use." msgstr "Der Gruppenname wird bereits verwendet." #: ../lib/modules/zarafaDynamicGroup.inc:180 -#: ../lib/modules/zarafaDynamicGroup.inc:181 ../lib/modules/posixGroup.inc:762 -#: ../lib/modules/posixGroup.inc:763 ../lib/modules/pykotaGroup.inc:168 +#: ../lib/modules/zarafaDynamicGroup.inc:181 ../lib/modules/posixGroup.inc:711 +#: ../lib/modules/posixGroup.inc:712 ../lib/modules/pykotaGroup.inc:168 #: ../lib/modules/pykotaGroup.inc:169 ../lib/modules/pykotaGroup.inc:172 #: ../lib/modules/pykotaGroup.inc:173 ../lib/modules/kopanoDynamicGroup.inc:184 -#: ../lib/modules/kopanoDynamicGroup.inc:185 ../lib/modules/nisnetgroup.inc:154 +#: ../lib/modules/kopanoDynamicGroup.inc:185 ../lib/modules/nisnetgroup.inc:153 #: ../lib/modules/windowsGroup.inc:279 ../lib/modules/windowsGroup.inc:280 msgid "" "Group name contains invalid characters. Valid characters are: a-z, A-Z, 0-9 " @@ -5698,7 +5701,7 @@ msgstr "" msgid "Group name for NIS." msgstr "Gruppenname für NIS." -#: ../lib/modules/posixGroup.inc:761 +#: ../lib/modules/posixGroup.inc:710 msgid "Group name in use. Selected next free group name." msgstr "" "Gruppenname wird bereits verwendet. Nächster freier Gruppenname wurde " @@ -5713,7 +5716,7 @@ msgstr "" "Gruppenname der zu erstellenden Gruppe. Gültige Zeichen sind: a-z, A-Z, 0-9 " "und .-_." -#: ../lib/modules/posixGroup.inc:498 +#: ../lib/modules/posixGroup.inc:447 msgid "" "Group name of the group which should be created. Valid characters are: a-z, " "A-Z, 0-9 and .-_ . If group name is already used group name will be expanded " @@ -5750,31 +5753,31 @@ msgstr "Namensgruppen" msgid "Group of unique names" msgstr "Gruppe eindeutiger Namen" -#: ../lib/modules/posixGroup.inc:461 ../lib/modules/posixGroup.inc:485 +#: ../lib/modules/posixGroup.inc:410 ../lib/modules/posixGroup.inc:434 msgid "Group password" msgstr "Gruppenpasswort" #: ../lib/modules/windowsGroup.inc:151 ../lib/modules/windowsGroup.inc:190 #: ../lib/modules/windowsGroup.inc:258 ../lib/modules/windowsGroup.inc:334 -#: ../lib/modules/windowsGroup.inc:925 +#: ../lib/modules/windowsGroup.inc:907 msgid "Group scope" msgstr "Gruppenbereich" -#: ../lib/modules/sambaGroupMapping.inc:357 ../lib/modules/windowsGroup.inc:147 +#: ../lib/modules/sambaGroupMapping.inc:361 ../lib/modules/windowsGroup.inc:147 #: ../lib/modules/windowsGroup.inc:198 ../lib/modules/windowsGroup.inc:257 -#: ../lib/modules/windowsGroup.inc:337 ../lib/modules/windowsGroup.inc:926 +#: ../lib/modules/windowsGroup.inc:337 ../lib/modules/windowsGroup.inc:908 msgid "Group type" msgstr "Gruppentyp" -#: ../lib/types/group.inc:69 ../lib/modules/posixGroup.inc:556 +#: ../lib/types/group.inc:69 ../lib/modules/posixGroup.inc:505 #: ../lib/modules/zarafaGroup.inc:737 ../lib/modules/kopanoGroup.inc:698 -#: ../lib/modules/posixAccount.inc:3258 ../lib/modules/windowsUser.inc:237 +#: ../lib/modules/posixAccount.inc:3217 ../lib/modules/windowsUser.inc:237 #: ../lib/modules/windowsUser.inc:240 ../lib/modules/windowsUser.inc:594 -#: ../lib/modules/windowsUser.inc:864 ../lib/modules/windowsUser.inc:1310 -#: ../lib/modules/windowsUser.inc:1769 ../lib/modules/windowsUser.inc:2780 -#: ../lib/modules/windowsUser.inc:2810 +#: ../lib/modules/windowsUser.inc:864 ../lib/modules/windowsUser.inc:1312 +#: ../lib/modules/windowsUser.inc:1774 ../lib/modules/windowsUser.inc:2752 +#: ../lib/modules/windowsUser.inc:2782 #: ../lib/modules/generalInformation.inc:111 -#: ../lib/modules/groupOfNamesUser.inc:537 ../lib/modules/windowsGroup.inc:509 +#: ../lib/modules/groupOfNamesUser.inc:537 ../lib/modules/windowsGroup.inc:505 msgid "Groups" msgstr "Gruppen" @@ -5782,10 +5785,10 @@ msgstr "Gruppen" msgid "Groups (read-only)" msgstr "Gruppen (nur lesen)" -#: ../lib/types/gon.inc:54 ../lib/modules/posixAccount.inc:256 -#: ../lib/modules/posixAccount.inc:305 ../lib/modules/posixAccount.inc:1833 -#: ../lib/modules/posixAccount.inc:2022 ../lib/modules/posixAccount.inc:2163 -#: ../lib/modules/posixAccount.inc:2259 ../lib/modules/groupOfNamesUser.inc:54 +#: ../lib/types/gon.inc:54 ../lib/modules/posixAccount.inc:261 +#: ../lib/modules/posixAccount.inc:310 ../lib/modules/posixAccount.inc:1811 +#: ../lib/modules/posixAccount.inc:1988 ../lib/modules/posixAccount.inc:2121 +#: ../lib/modules/posixAccount.inc:2217 ../lib/modules/groupOfNamesUser.inc:54 #: ../lib/modules/groupOfNamesUser.inc:56 #: ../lib/modules/groupOfNamesUser.inc:60 #: ../lib/modules/groupOfNamesUser.inc:64 @@ -5799,38 +5802,38 @@ msgstr "Namensgruppen" msgid "H-Node (0x08)" msgstr "H-Knoten (0x08)" -#: ../templates/config/confmain.php:366 ../lib/passwordExpirationJob.inc:88 +#: ../templates/config/confmain.php:370 ../lib/passwordExpirationJob.inc:88 #: ../lib/modules/selfRegistration.inc:221 -#: ../lib/modules/passwordSelfReset.inc:667 -#: ../lib/modules/passwordSelfReset.inc:683 ../help/help.inc:337 +#: ../lib/modules/passwordSelfReset.inc:665 +#: ../lib/modules/passwordSelfReset.inc:681 ../help/help.inc:343 msgid "HTML format" msgstr "HTML Format" -#: ../templates/config/confmain.php:454 -#: ../templates/selfService/adminMain.php:442 ../help/help.inc:132 +#: ../templates/config/confmain.php:458 +#: ../templates/selfService/adminMain.php:448 ../help/help.inc:132 msgid "HTTP authentication" msgstr "HTTP-Authentifizierung" -#: ../lib/modules/kopanoServer.inc:68 ../lib/modules/kopanoServer.inc:100 -#: ../lib/modules/kopanoServer.inc:135 ../lib/modules/kopanoServer.inc:150 -#: ../lib/modules/kopanoServer.inc:172 ../lib/modules/kopanoServer.inc:327 +#: ../lib/modules/kopanoServer.inc:67 ../lib/modules/kopanoServer.inc:99 +#: ../lib/modules/kopanoServer.inc:134 ../lib/modules/kopanoServer.inc:149 +#: ../lib/modules/kopanoServer.inc:167 ../lib/modules/kopanoServer.inc:307 #: ../lib/modules/zarafaServer.inc:68 ../lib/modules/zarafaServer.inc:100 #: ../lib/modules/zarafaServer.inc:135 ../lib/modules/zarafaServer.inc:150 #: ../lib/modules/zarafaServer.inc:172 ../lib/modules/zarafaServer.inc:327 msgid "HTTP port" msgstr "HTTP-Port" -#: ../lib/modules/quota.inc:744 ../lib/modules/systemQuotas.inc:372 +#: ../lib/modules/quota.inc:713 ../lib/modules/systemQuotas.inc:372 msgid "Hard block" msgstr "Hartes Block-Limit" #: ../lib/modules/quota.inc:123 ../lib/modules/quota.inc:124 -#: ../lib/modules/quota.inc:488 ../lib/modules/quota.inc:596 +#: ../lib/modules/quota.inc:467 ../lib/modules/quota.inc:567 #: ../lib/modules/systemQuotas.inc:128 msgid "Hard block limit" msgstr "Hartes Block-Limit" -#: ../lib/modules/quota.inc:746 ../lib/modules/systemQuotas.inc:374 +#: ../lib/modules/quota.inc:715 ../lib/modules/systemQuotas.inc:374 msgid "Hard inode" msgstr "Hartes Inode-Limit" @@ -5838,8 +5841,8 @@ msgstr "Hartes Inode-Limit" msgid "Hard inode (files) limit" msgstr "Hartes Inode-Limit (Dateien)" -#: ../lib/modules/quota.inc:145 ../lib/modules/quota.inc:492 -#: ../lib/modules/quota.inc:602 ../lib/modules/systemQuotas.inc:132 +#: ../lib/modules/quota.inc:145 ../lib/modules/quota.inc:468 +#: ../lib/modules/quota.inc:573 ../lib/modules/systemQuotas.inc:132 msgid "Hard inode limit" msgstr "Hartes Inode-Limit" @@ -5850,7 +5853,7 @@ msgstr "Hat Untereinträge" #: ../lib/modules/selfRegistration.inc:105 #: ../lib/modules/selfRegistration.inc:210 #: ../lib/modules/passwordSelfReset.inc:159 -#: ../lib/modules/passwordSelfReset.inc:655 +#: ../lib/modules/passwordSelfReset.inc:653 msgid "Header" msgstr "Überschrift" @@ -5893,7 +5896,7 @@ msgstr "" msgid "Here you can enter a description for this DHCP entry." msgstr "Hier können Sie eine Beschreibung für den DHCP-Eintrag eintragen." -#: ../lib/modules/nisnetgroup.inc:91 ../lib/modules/organizationalRole.inc:94 +#: ../lib/modules/nisnetgroup.inc:90 ../lib/modules/organizationalRole.inc:94 #: ../lib/modules/groupOfNames.inc:93 msgid "Here you can enter a description for this group." msgstr "Hier können Sie eine Beschreibung für die Gruppe eintragen." @@ -5903,9 +5906,9 @@ msgid "Here you can enter a description for this role." msgstr "Hier können Sie eine Beschreibung für die Sudo-Rolle eintragen." #: ../lib/modules/pykotaPrinter.inc:128 ../lib/modules/zarafaContact.inc:95 -#: ../lib/modules/posixGroup.inc:523 ../lib/modules/zarafaUser.inc:159 +#: ../lib/modules/posixGroup.inc:472 ../lib/modules/zarafaUser.inc:159 #: ../lib/modules/kopanoContact.inc:95 ../lib/modules/sambaGroupMapping.inc:171 -#: ../lib/modules/nisnetgroup.inc:108 ../lib/modules/kopanoUser.inc:159 +#: ../lib/modules/nisnetgroup.inc:107 ../lib/modules/kopanoUser.inc:159 #: ../lib/modules/organizationalRole.inc:106 #: ../lib/modules/groupOfNames.inc:117 ../lib/modules/nisMailAlias.inc:93 msgid "" @@ -5915,7 +5918,7 @@ msgstr "" "Hier können Sie einen Filtertext eingeben. Es werden dann nur Einträge " "angezeigt, die diesen Wert enthalten." -#: ../lib/modules/posixAccount.inc:349 ../lib/modules/groupOfNamesUser.inc:65 +#: ../lib/modules/posixAccount.inc:354 ../lib/modules/groupOfNamesUser.inc:65 msgid "" "Here you can enter a list of additional group memberships. The group names " "are separated by commas." @@ -5947,7 +5950,7 @@ msgstr "Hier können Sie eine Seriennummer für das Gerät eintragen." msgid "Here you can enter additional web sites for the user." msgstr "Hier können Sie zusätzliche Webseiten für den Benutzer eintragen." -#: ../help/help.inc:267 +#: ../help/help.inc:269 msgid "" "Here you can enter an alternative mail address for the password. To use the " "user's primary email address please leave the field blank." @@ -5988,7 +5991,7 @@ msgstr "Hier können Sie einzelne Kopano-Funktionen (de)aktivieren." msgid "Here you can explicitly enable and disable Zarafa features." msgstr "Hier können Sie einzelne Zarafa-Funktionen (de)aktivieren." -#: ../help/help.inc:265 +#: ../help/help.inc:267 msgid "" "Here you can export PDF structures to other server profiles (overwrite " "existing). You may also export a structure to the global templates. In this " @@ -6013,7 +6016,7 @@ msgstr "" "alle Serverprofile kopiert, die noch kein Accountprofil gleichen Namens " "haben." -#: ../help/help.inc:263 +#: ../help/help.inc:265 msgid "" "Here you can import PDF structures from other server profiles (overwrite " "existing)." @@ -6029,7 +6032,7 @@ msgstr "" "Damit können Sie Accountprofile aus anderen Serverprofilen importieren " "(überschreibt bestehende Accountprofile)." -#: ../lib/lists.inc:470 ../help/help.inc:179 +#: ../lib/lists.inc:458 ../help/help.inc:179 msgid "" "Here you can input simple filter expressions (e.g. 'value' or 'v*'). The " "filter is case-insensitive." @@ -6037,7 +6040,7 @@ msgstr "" "Hier können Sie einfache Filter angeben (z.B. 'wert' oder 'w*'). LAM filtert " "ohne Berücksichtigung von Groß-/Kleinschreibung." -#: ../help/help.inc:251 +#: ../help/help.inc:253 msgid "" "Here you can load an account profile to set default settings for your " "account. The \"default\" profile is automatically loaded for new accounts." @@ -6053,7 +6056,7 @@ msgstr "Hier können Sie Ihre Accountprofile verwalten." msgid "Here you can overwrite the display name for this account type." msgstr "Hier können Sie den Anzeigename für diesen Kontotyp überschreiben." -#: ../help/help.inc:253 +#: ../help/help.inc:255 msgid "" "Here you can select a PDF structure and export the account to a PDF file." msgstr "" @@ -6099,7 +6102,7 @@ msgstr "" msgid "Here you can set the URL to a custom icon (32x32px) for this module." msgstr "Hier können Sie die URL für ein eigenes Symbol (32x32px) eintragen." -#: ../help/help.inc:291 +#: ../help/help.inc:293 msgid "" "Here you can specify additional CSS links to change the layout of the self " "service pages. This is useful to adapt them to your corporate design. Please " @@ -6118,7 +6121,7 @@ msgstr "" "Zeichenklassen sind: Kleinbuchstaben, Großbuchstaben, Zahlen und " "Sonderzeichen" -#: ../lib/modules/nisnetgroup.inc:95 +#: ../lib/modules/nisnetgroup.inc:94 msgid "" "Here you can specify subgroups which are included in this NIS netgroup. All " "members of the subgroups will be treated as members of this group." @@ -6146,7 +6149,7 @@ msgstr "" "Account anmelden darf. Dabei steht \"*\" für alle Hosts. Sie können auch \"!" "\" vor einen Hostnamen stellen und ihn damit ausschließen." -#: ../lib/modules/sambaDomain.inc:102 +#: ../lib/modules/sambaDomain.inc:101 msgid "" "Here you can specify the minimum number of characters for a user password." msgstr "Bestimmt die minimale Länge für Benutzerpasswörter." @@ -6194,15 +6197,15 @@ msgstr "Hier können Sie eine neue Datei hochladen." #: ../lib/modules/zarafaUser.inc:390 ../lib/modules/zarafaUser.inc:607 #: ../lib/modules/zarafaUser.inc:1315 ../lib/modules/kopanoContact.inc:74 #: ../lib/modules/kopanoContact.inc:155 ../lib/modules/kopanoContact.inc:163 -#: ../lib/modules/kopanoContact.inc:246 ../lib/modules/kopanoContact.inc:597 +#: ../lib/modules/kopanoContact.inc:246 ../lib/modules/kopanoContact.inc:594 #: ../lib/modules/kopanoDynamicGroup.inc:82 #: ../lib/modules/kopanoDynamicGroup.inc:139 #: ../lib/modules/kopanoDynamicGroup.inc:172 #: ../lib/modules/kopanoDynamicGroup.inc:220 #: ../lib/modules/kopanoDynamicGroup.inc:364 ../lib/modules/kopanoUser.inc:98 #: ../lib/modules/kopanoUser.inc:314 ../lib/modules/kopanoUser.inc:363 -#: ../lib/modules/kopanoUser.inc:390 ../lib/modules/kopanoUser.inc:607 -#: ../lib/modules/kopanoUser.inc:1315 ../lib/modules/kopanoAddressList.inc:66 +#: ../lib/modules/kopanoUser.inc:390 ../lib/modules/kopanoUser.inc:572 +#: ../lib/modules/kopanoUser.inc:1234 ../lib/modules/kopanoAddressList.inc:66 #: ../lib/modules/kopanoAddressList.inc:111 #: ../lib/modules/kopanoAddressList.inc:142 #: ../lib/modules/kopanoAddressList.inc:179 @@ -6219,33 +6222,33 @@ msgstr "" "diese Einstellung wenn Sie z.B. nur Gruppen anzeigen möchten und Mitglieder " "trotzdem änderbar sein sollen." -#: ../lib/modules/kopanoServer.inc:88 ../lib/modules/kopanoServer.inc:393 +#: ../lib/modules/kopanoServer.inc:87 ../lib/modules/kopanoServer.inc:373 #: ../lib/modules/nisMailAliasUser.inc:104 -#: ../lib/modules/nisMailAliasUser.inc:565 +#: ../lib/modules/nisMailAliasUser.inc:558 #: ../lib/modules/asteriskAccount.inc:242 -#: ../lib/modules/asteriskAccount.inc:1226 ../lib/modules/qmailUser.inc:161 -#: ../lib/modules/qmailUser.inc:1212 ../lib/modules/zarafaGroup.inc:115 +#: ../lib/modules/asteriskAccount.inc:1227 ../lib/modules/qmailUser.inc:161 +#: ../lib/modules/qmailUser.inc:1052 ../lib/modules/zarafaGroup.inc:115 #: ../lib/modules/zarafaGroup.inc:761 ../lib/modules/kopanoGroup.inc:114 #: ../lib/modules/kopanoGroup.inc:722 ../lib/modules/sambaSamAccount.inc:401 -#: ../lib/modules/sambaSamAccount.inc:1899 ../lib/modules/posixAccount.inc:319 -#: ../lib/modules/posixAccount.inc:2250 ../lib/modules/posixAccount.inc:2319 +#: ../lib/modules/sambaSamAccount.inc:1946 ../lib/modules/posixAccount.inc:324 +#: ../lib/modules/posixAccount.inc:2208 ../lib/modules/posixAccount.inc:2277 #: ../lib/modules/zarafaUser.inc:150 ../lib/modules/zarafaUser.inc:1816 -#: ../lib/modules/windowsUser.inc:255 ../lib/modules/windowsUser.inc:3626 -#: ../lib/modules/inetOrgPerson.inc:749 ../lib/modules/inetOrgPerson.inc:3855 +#: ../lib/modules/windowsUser.inc:255 ../lib/modules/windowsUser.inc:3598 +#: ../lib/modules/inetOrgPerson.inc:749 ../lib/modules/inetOrgPerson.inc:3856 #: ../lib/modules/freeRadius.inc:127 ../lib/modules/freeRadius.inc:298 -#: ../lib/modules/kopanoUser.inc:150 ../lib/modules/kopanoUser.inc:1827 +#: ../lib/modules/kopanoUser.inc:150 ../lib/modules/kopanoUser.inc:1746 #: ../lib/modules/zarafaServer.inc:88 ../lib/modules/zarafaServer.inc:394 -#: ../lib/modules/qmailGroup.inc:84 ../lib/modules/qmailGroup.inc:1010 +#: ../lib/modules/qmailGroup.inc:84 ../lib/modules/qmailGroup.inc:861 #: ../lib/modules/organizationalRole.inc:85 #: ../lib/modules/organizationalRole.inc:732 #: ../lib/modules/passwordSelfReset.inc:163 #: ../lib/modules/passwordSelfReset.inc:301 ../lib/modules/windowsGroup.inc:115 -#: ../lib/modules/windowsGroup.inc:1097 ../lib/modules/groupOfNames.inc:84 +#: ../lib/modules/windowsGroup.inc:1078 ../lib/modules/groupOfNames.inc:84 #: ../lib/modules/groupOfNames.inc:1041 msgid "Hidden options" msgstr "Versteckte Optionen" -#: ../templates/config/confmain.php:383 +#: ../templates/config/confmain.php:387 msgid "Hidden tools" msgstr "Versteckte Werkzeuge" @@ -6253,8 +6256,8 @@ msgstr "Versteckte Werkzeuge" msgid "Hide" msgstr "Verstecken" -#: ../lib/modules/customScripts.inc:88 ../lib/modules/customScripts.inc:100 -#: ../lib/modules/customScripts.inc:292 +#: ../lib/modules/customScripts.inc:87 ../lib/modules/customScripts.inc:99 +#: ../lib/modules/customScripts.inc:297 msgid "Hide command in messages" msgstr "Kommando in Meldungen ausblenden" @@ -6262,7 +6265,7 @@ msgstr "Kommando in Meldungen ausblenden" msgid "Hide internal attributes" msgstr "Interne Attribute verstecken" -#: ../lib/modules/customScripts.inc:92 ../lib/modules/customScripts.inc:294 +#: ../lib/modules/customScripts.inc:91 ../lib/modules/customScripts.inc:299 msgid "Hide tab" msgstr "Tab verstecken" @@ -6295,7 +6298,7 @@ msgid "" msgstr "" "Hinweis: Wählen Sie genau eine strukturelle Objektklasse (fett markiert)." -#: ../lib/modules/posixAccount.inc:435 ../lib/modules/windowsUser.inc:238 +#: ../lib/modules/posixAccount.inc:440 ../lib/modules/windowsUser.inc:238 #: ../lib/modules/groupOfNamesUser.inc:61 msgid "Hold the CTRL-key to (de)select multiple groups." msgstr "Halten Sie Strg gedrückt um mehrere Gruppen zu markieren." @@ -6306,14 +6309,14 @@ msgstr "Halten Sie Strg gedrückt um mehrere Rollen zu markieren." #: ../lib/types/user.inc:103 ../lib/modules/sambaSamAccount.inc:232 #: ../lib/modules/sambaSamAccount.inc:356 -#: ../lib/modules/sambaSamAccount.inc:1459 -#: ../lib/modules/sambaSamAccount.inc:1940 ../lib/modules/posixAccount.inc:90 +#: ../lib/modules/sambaSamAccount.inc:1518 +#: ../lib/modules/sambaSamAccount.inc:1987 ../lib/modules/posixAccount.inc:90 #: ../lib/modules/posixAccount.inc:91 ../lib/modules/posixAccount.inc:93 -#: ../lib/modules/posixAccount.inc:232 ../lib/modules/posixAccount.inc:300 -#: ../lib/modules/posixAccount.inc:352 ../lib/modules/posixAccount.inc:356 -#: ../lib/modules/posixAccount.inc:360 ../lib/modules/posixAccount.inc:364 -#: ../lib/modules/posixAccount.inc:1664 ../lib/modules/posixAccount.inc:1927 -#: ../lib/modules/posixAccount.inc:2029 ../lib/modules/posixAccount.inc:2150 +#: ../lib/modules/posixAccount.inc:237 ../lib/modules/posixAccount.inc:305 +#: ../lib/modules/posixAccount.inc:357 ../lib/modules/posixAccount.inc:361 +#: ../lib/modules/posixAccount.inc:365 ../lib/modules/posixAccount.inc:369 +#: ../lib/modules/posixAccount.inc:1649 ../lib/modules/posixAccount.inc:1902 +#: ../lib/modules/posixAccount.inc:1995 ../lib/modules/posixAccount.inc:2108 #: ../lib/modules/courierMailAccount.inc:136 #: ../lib/modules/courierMailAccount.inc:244 #: ../lib/modules/courierMailAccount.inc:423 @@ -6321,7 +6324,7 @@ msgstr "Halten Sie Strg gedrückt um mehrere Rollen zu markieren." #: ../lib/modules/courierMailAccount.inc:474 ../lib/modules/windowsUser.inc:266 #: ../lib/modules/windowsUser.inc:588 ../lib/modules/windowsUser.inc:767 #: ../lib/modules/windowsUser.inc:867 ../lib/modules/windowsUser.inc:1029 -#: ../lib/modules/windowsUser.inc:1265 ../lib/modules/windowsUser.inc:2774 +#: ../lib/modules/windowsUser.inc:1272 ../lib/modules/windowsUser.inc:2746 msgid "Home directory" msgstr "Heimatverzeichnis" @@ -6338,15 +6341,15 @@ msgstr "" #: ../lib/modules/sambaSamAccount.inc:233 #: ../lib/modules/sambaSamAccount.inc:304 #: ../lib/modules/sambaSamAccount.inc:494 -#: ../lib/modules/sambaSamAccount.inc:1148 -#: ../lib/modules/sambaSamAccount.inc:1467 -#: ../lib/modules/sambaSamAccount.inc:1720 -#: ../lib/modules/sambaSamAccount.inc:1903 -#: ../lib/modules/sambaSamAccount.inc:1924 -#: ../lib/modules/sambaSamAccount.inc:1941 ../lib/modules/windowsUser.inc:263 +#: ../lib/modules/sambaSamAccount.inc:1189 +#: ../lib/modules/sambaSamAccount.inc:1528 +#: ../lib/modules/sambaSamAccount.inc:1767 +#: ../lib/modules/sambaSamAccount.inc:1950 +#: ../lib/modules/sambaSamAccount.inc:1971 +#: ../lib/modules/sambaSamAccount.inc:1988 ../lib/modules/windowsUser.inc:263 #: ../lib/modules/windowsUser.inc:582 ../lib/modules/windowsUser.inc:866 -#: ../lib/modules/windowsUser.inc:1263 ../lib/modules/windowsUser.inc:2775 -#: ../lib/modules/windowsUser.inc:2819 +#: ../lib/modules/windowsUser.inc:1270 ../lib/modules/windowsUser.inc:2747 +#: ../lib/modules/windowsUser.inc:2791 msgid "Home drive" msgstr "Heimatlaufwerk" @@ -6354,10 +6357,10 @@ msgstr "Heimatlaufwerk" #: ../lib/modules/sambaSamAccount.inc:219 #: ../lib/modules/sambaSamAccount.inc:307 #: ../lib/modules/sambaSamAccount.inc:502 -#: ../lib/modules/sambaSamAccount.inc:1156 -#: ../lib/modules/sambaSamAccount.inc:1724 -#: ../lib/modules/sambaSamAccount.inc:1904 -#: ../lib/modules/sambaSamAccount.inc:1923 +#: ../lib/modules/sambaSamAccount.inc:1197 +#: ../lib/modules/sambaSamAccount.inc:1771 +#: ../lib/modules/sambaSamAccount.inc:1951 +#: ../lib/modules/sambaSamAccount.inc:1970 msgid "Home path" msgstr "Heimatverzeichnis" @@ -6373,8 +6376,8 @@ msgstr "Dies ist der Heimatserver des Benutzers." #: ../lib/modules/inetOrgPerson.inc:355 ../lib/modules/inetOrgPerson.inc:513 #: ../lib/modules/inetOrgPerson.inc:693 ../lib/modules/inetOrgPerson.inc:697 #: ../lib/modules/inetOrgPerson.inc:1395 ../lib/modules/inetOrgPerson.inc:1398 -#: ../lib/modules/inetOrgPerson.inc:1958 ../lib/modules/inetOrgPerson.inc:2603 -#: ../lib/modules/inetOrgPerson.inc:3869 ../lib/modules/inetOrgPerson.inc:3908 +#: ../lib/modules/inetOrgPerson.inc:1959 ../lib/modules/inetOrgPerson.inc:2604 +#: ../lib/modules/inetOrgPerson.inc:3870 ../lib/modules/inetOrgPerson.inc:3909 msgid "Home telephone number" msgstr "Telefon privat" @@ -6390,8 +6393,8 @@ msgstr "Heimatverzeichnis enthält ungültige Zeichen." #: ../lib/modules/asteriskAccount.inc:266 #: ../lib/modules/asteriskAccount.inc:316 #: ../lib/modules/asteriskAccount.inc:589 -#: ../lib/modules/asteriskAccount.inc:983 ../lib/modules/nisnetgroup.inc:224 -#: ../lib/modules/nisnetgroup.inc:592 ../lib/modules/fixed_ip.inc:604 +#: ../lib/modules/asteriskAccount.inc:984 ../lib/modules/nisnetgroup.inc:226 +#: ../lib/modules/nisnetgroup.inc:576 ../lib/modules/fixed_ip.inc:604 #: ../lib/modules/hostObject.inc:144 msgid "Host" msgstr "Host" @@ -6409,8 +6412,8 @@ msgstr "Anzahl Hosts: %s" msgid "Host description" msgstr "Hostbeschreibung" -#: ../lib/modules/account.inc:89 ../lib/modules/posixAccount.inc:453 -#: ../lib/modules/posixAccount.inc:461 +#: ../lib/modules/account.inc:89 ../lib/modules/posixAccount.inc:458 +#: ../lib/modules/posixAccount.inc:466 msgid "Host description. If left empty host name will be used." msgstr "Hostbeschreibung. Falls leer wird der Hostname verwendet." @@ -6421,13 +6424,13 @@ msgstr "Hostliste" #: ../lib/types/host.inc:95 ../lib/types/bind.inc:80 #: ../lib/modules/account.inc:84 ../lib/modules/account.inc:216 -#: ../lib/modules/windowsHost.inc:74 ../lib/modules/windowsHost.inc:94 -#: ../lib/modules/windowsHost.inc:119 ../lib/modules/windowsHost.inc:131 -#: ../lib/modules/windowsHost.inc:142 ../lib/modules/windowsHost.inc:284 +#: ../lib/modules/windowsHost.inc:73 ../lib/modules/windowsHost.inc:93 +#: ../lib/modules/windowsHost.inc:118 ../lib/modules/windowsHost.inc:130 +#: ../lib/modules/windowsHost.inc:141 ../lib/modules/windowsHost.inc:287 #: ../lib/modules/posixAccount.inc:105 ../lib/modules/posixAccount.inc:106 -#: ../lib/modules/posixAccount.inc:108 ../lib/modules/posixAccount.inc:267 -#: ../lib/modules/posixAccount.inc:290 ../lib/modules/posixAccount.inc:448 -#: ../lib/modules/posixAccount.inc:1627 ../lib/modules/posixAccount.inc:2134 +#: ../lib/modules/posixAccount.inc:108 ../lib/modules/posixAccount.inc:272 +#: ../lib/modules/posixAccount.inc:295 ../lib/modules/posixAccount.inc:453 +#: ../lib/modules/posixAccount.inc:1606 ../lib/modules/posixAccount.inc:2092 #: ../lib/modules/bindDLZ.inc:93 ../lib/modules/bindDLZ.inc:156 #: ../lib/modules/bindDLZ.inc:160 ../lib/modules/bindDLZ.inc:264 #: ../lib/modules/bindDLZ.inc:351 ../lib/modules/bindDLZ.inc:492 @@ -6437,8 +6440,8 @@ msgstr "Hostliste" #: ../lib/modules/bindDLZ.inc:1828 ../lib/modules/nisNetGroupUser.inc:97 #: ../lib/modules/nisNetGroupUser.inc:185 #: ../lib/modules/nisNetGroupUser.inc:435 -#: ../lib/modules/nisNetGroupUser.inc:478 ../lib/modules/nisnetgroup.inc:156 -#: ../lib/modules/nisnetgroup.inc:494 +#: ../lib/modules/nisNetGroupUser.inc:478 ../lib/modules/nisnetgroup.inc:155 +#: ../lib/modules/nisnetgroup.inc:481 msgid "Host name" msgstr "Hostname" @@ -6446,9 +6449,9 @@ msgstr "Hostname" msgid "Host name already exists!" msgstr "Host-Name besteht schon!" -#: ../lib/modules/windowsHost.inc:131 ../lib/modules/windowsHost.inc:132 +#: ../lib/modules/windowsHost.inc:130 ../lib/modules/windowsHost.inc:131 #: ../lib/modules/posixAccount.inc:106 ../lib/modules/posixAccount.inc:110 -#: ../lib/modules/nisNetGroupUser.inc:97 ../lib/modules/nisnetgroup.inc:156 +#: ../lib/modules/nisNetGroupUser.inc:97 ../lib/modules/nisnetgroup.inc:155 msgid "" "Host name contains invalid characters. Valid characters are: a-z, A-Z, 0-9 " "and .-_ !" @@ -6463,11 +6466,11 @@ msgstr "" "Hostname wird bereits verwendet (%s). Nächster freier Hostname wurde " "ausgewählt." -#: ../lib/modules/sambaSamAccount.inc:1044 +#: ../lib/modules/sambaSamAccount.inc:1076 msgid "Host name must end with $!" msgstr "Der Hostname muss mit einem $ enden!" -#: ../lib/modules/account.inc:85 ../lib/modules/posixAccount.inc:449 +#: ../lib/modules/account.inc:85 ../lib/modules/posixAccount.inc:454 msgid "" "Host name of the host which should be created. Valid characters are: a-z,A-" "Z,0-9, .-_$. Host names are always ending with $. If last character is not $ " @@ -6480,10 +6483,10 @@ msgstr "" #: ../lib/types/host.inc:55 ../lib/modules/posixAccount.inc:85 #: ../lib/modules/posixAccount.inc:86 ../lib/modules/posixAccount.inc:88 -#: ../lib/modules/posixAccount.inc:2272 ../lib/modules/sudoRole.inc:77 +#: ../lib/modules/posixAccount.inc:2230 ../lib/modules/sudoRole.inc:77 #: ../lib/modules/sudoRole.inc:102 ../lib/modules/sudoRole.inc:164 #: ../lib/modules/sudoRole.inc:216 ../lib/modules/sudoRole.inc:228 -#: ../lib/modules/sudoRole.inc:342 ../lib/modules/sudoRole.inc:748 +#: ../lib/modules/sudoRole.inc:302 ../lib/modules/sudoRole.inc:455 #: ../lib/modules/fixed_ip.inc:85 ../lib/modules/hostObject.inc:68 #: ../lib/modules/hostObject.inc:78 msgid "Hosts" @@ -6493,22 +6496,22 @@ msgstr "Hosts" msgid "I am out of office." msgstr "Ich bin derzeit nicht im Büro." -#: ../lib/modules/zarafaContact.inc:183 ../lib/modules/posixGroup.inc:755 +#: ../lib/modules/zarafaContact.inc:183 ../lib/modules/posixGroup.inc:704 #: ../lib/modules/posixAccount.inc:96 ../lib/modules/kopanoContact.inc:183 msgid "ID is already in use" msgstr "ID wird bereits verwendet" -#: ../lib/modules/posixGroup.inc:753 ../lib/modules/posixGroup.inc:754 -#: ../lib/modules/posixGroup.inc:755 ../lib/modules/posixGroup.inc:902 +#: ../lib/modules/posixGroup.inc:702 ../lib/modules/posixGroup.inc:703 +#: ../lib/modules/posixGroup.inc:704 ../lib/modules/posixGroup.inc:851 #: ../lib/modules/posixAccount.inc:94 ../lib/modules/posixAccount.inc:95 -#: ../lib/modules/posixAccount.inc:96 ../lib/modules/posixAccount.inc:1141 -#: ../lib/modules/posixAccount.inc:1146 +#: ../lib/modules/posixAccount.inc:96 ../lib/modules/posixAccount.inc:1121 +#: ../lib/modules/posixAccount.inc:1126 msgid "ID-Number" msgstr "ID-Nummer" #: ../lib/modules/zarafaUser.inc:323 ../lib/modules/zarafaUser.inc:674 #: ../lib/modules/zarafaUser.inc:1369 ../lib/modules/kopanoUser.inc:323 -#: ../lib/modules/kopanoUser.inc:674 ../lib/modules/kopanoUser.inc:1370 +#: ../lib/modules/kopanoUser.inc:632 ../lib/modules/kopanoUser.inc:1289 msgid "IMAP" msgstr "IMAP" @@ -6526,8 +6529,8 @@ msgstr "IMAP-Passworteingabe" #: ../lib/modules/asteriskAccount.inc:293 #: ../lib/modules/asteriskAccount.inc:520 #: ../lib/modules/asteriskAccount.inc:744 -#: ../lib/modules/asteriskAccount.inc:1010 -#: ../lib/modules/asteriskAccount.inc:1254 ../lib/modules/bindDLZXfr.inc:59 +#: ../lib/modules/asteriskAccount.inc:1011 +#: ../lib/modules/asteriskAccount.inc:1255 ../lib/modules/bindDLZXfr.inc:59 #: ../lib/modules/bindDLZXfr.inc:74 ../lib/modules/bindDLZXfr.inc:83 #: ../lib/modules/bindDLZXfr.inc:95 ../lib/modules/bindDLZXfr.inc:106 #: ../lib/modules/bindDLZXfr.inc:152 ../lib/modules/bindDLZ.inc:97 @@ -6578,7 +6581,7 @@ msgid "Icon" msgstr "Symbol" #: ../lib/modules/passwordSelfReset.inc:147 -#: ../lib/modules/passwordSelfReset.inc:636 +#: ../lib/modules/passwordSelfReset.inc:634 msgid "Identification method" msgstr "Identifizierungsmethode" @@ -6587,8 +6590,8 @@ msgid "Identifier" msgstr "Bezeichner" #: ../lib/modules/sambaSamAccount.inc:240 -#: ../lib/modules/sambaSamAccount.inc:1485 -#: ../lib/modules/sambaSamAccount.inc:1952 +#: ../lib/modules/sambaSamAccount.inc:1546 +#: ../lib/modules/sambaSamAccount.inc:1999 msgid "Idle time limit" msgstr "Zeitlimit für Leerlauf" @@ -6628,7 +6631,7 @@ msgid "If checked Unix password will also be used as Samba password." msgstr "" "Falls aktiviert wird das Unix-Passwort auch als Samba-Passwort verwendet." -#: ../lib/modules/posixAccount.inc:427 +#: ../lib/modules/posixAccount.inc:432 msgid "" "If checked account will be deactivated by putting a \"!\" before the " "encrypted password." @@ -6648,7 +6651,7 @@ msgstr "Falls angekreuzt läuft das Passwort nicht aus." msgid "If checked password does not expire. (Setting X-Flag)" msgstr "Falls angekreuzt läuft das Passwort nicht aus. (X-Flag wird gesetzt)" -#: ../help/help.inc:309 +#: ../help/help.inc:313 msgid "" "If checked then also users who did not setup a second factor are able to " "login." @@ -6689,7 +6692,7 @@ msgid "" msgstr "" "Falls angekreuzt, kann der Benutzer keine Einträge dieses Typs löschen." -#: ../lib/modules/posixGroup.inc:470 +#: ../lib/modules/posixGroup.inc:419 msgid "" "If empty GID number will be generated automaticly depending on your " "configuration settings." @@ -6702,7 +6705,7 @@ msgid "If empty GID number will be generated automaticly." msgstr "Falls leer wird die GID-Nummer automatisch generiert." #: ../lib/modules/qmailUser.inc:198 ../lib/modules/zarafaContact.inc:118 -#: ../lib/modules/posixAccount.inc:341 ../lib/modules/kopanoContact.inc:118 +#: ../lib/modules/posixAccount.inc:346 ../lib/modules/kopanoContact.inc:118 msgid "If empty UID number will be generated automaticly." msgstr "Falls leer wird die UID-Nummer automatisch generiert." @@ -6711,7 +6714,7 @@ msgstr "Falls leer wird die UID-Nummer automatisch generiert." msgid "If enabled the user is required to solve a captcha." msgstr "Bei Aktivieren dieser Option muss der Benutzer ein Captcha lösen." -#: ../lib/modules/ppolicy.inc:125 +#: ../lib/modules/ppolicy.inc:124 msgid "" "If enabled the user will not be allowed to login after there have been a " "specified number of consecutive failed login attempts." @@ -6735,7 +6738,7 @@ msgstr "" "Damit können Sie die Standardsprache erzwingen, der Benutzer hat dann keine " "Wahlmöglichkeit mehr." -#: ../lib/modules/ppolicy.inc:129 +#: ../lib/modules/ppolicy.inc:128 msgid "" "If enabled users must change their passwords when they first login after a " "password is set or reset by the administrator." @@ -6759,7 +6762,7 @@ msgstr "" "Falls aktiviert dann sind nur diese Klassen erlaubt. Bitte geben Sie eine " "Klasse pro Zeile ein." -#: ../lib/modules/sambaDomain.inc:110 +#: ../lib/modules/sambaDomain.inc:109 msgid "If set then users need to login to change their password." msgstr "" "Falls aktiviert, dann müssen sich Benutzer zur Passwortänderung anmelden." @@ -6862,7 +6865,7 @@ msgstr "" "Wenn Sie das Feld leer lassen wird LAM folgenden Wert einfügen: uidNumber*2 " "+ sambaAlgorithmicRidBase." -#: ../lib/modules/kopanoServer.inc:85 +#: ../lib/modules/kopanoServer.inc:84 msgid "" "If you run Kopano behind a reverse proxy then you can specify the server URL " "here (e.g. \"https://zproxy.example.com:237/z1\")." @@ -6933,7 +6936,7 @@ msgstr "" #: ../lib/modules/mitKerberos.inc:183 ../lib/modules/sambaSamAccount.inc:297 #: ../lib/modules/windowsUser.inc:235 ../lib/modules/heimdalKerberos.inc:127 -#: ../help/help.inc:259 +#: ../help/help.inc:261 msgid "" "If you set this option then the user has to change his password at the next " "login." @@ -7016,7 +7019,7 @@ msgid "Import" msgstr "Import" #: ../templates/pdfedit/pdfmain.php:256 ../templates/pdfedit/pdfmain.php:257 -#: ../help/help.inc:262 +#: ../help/help.inc:264 msgid "Import PDF structures" msgstr "PDF-Strukturen importieren" @@ -7059,7 +7062,7 @@ msgid "Inactive hosts will not be able to get an address from the DHCP server." msgstr "Inaktive Hosts bekommen keine Adresse vom DHCP-Server." #: ../templates/tools/importexport.php:280 -#: ../templates/3rdParty/pla/htdocs/export_form.php:87 ../help/help.inc:367 +#: ../templates/3rdParty/pla/htdocs/export_form.php:87 ../help/help.inc:373 msgid "Include system attributes" msgstr "Systemattribute miteinbeziehen" @@ -7069,8 +7072,8 @@ msgstr "Informationen über den LDAP-Server." #: ../lib/modules/sambaSamAccount.inc:235 #: ../lib/modules/sambaSamAccount.inc:362 -#: ../lib/modules/sambaSamAccount.inc:1471 -#: ../lib/modules/sambaSamAccount.inc:1947 +#: ../lib/modules/sambaSamAccount.inc:1532 +#: ../lib/modules/sambaSamAccount.inc:1994 msgid "Inherit client startup configuration" msgstr "Client-Startkonfiguration verwenden" @@ -7088,19 +7091,19 @@ msgstr "Initialzahlung" #: ../lib/modules/sambaSamAccount.inc:236 #: ../lib/modules/sambaSamAccount.inc:365 -#: ../lib/modules/sambaSamAccount.inc:1473 -#: ../lib/modules/sambaSamAccount.inc:1948 +#: ../lib/modules/sambaSamAccount.inc:1534 +#: ../lib/modules/sambaSamAccount.inc:1995 msgid "Initial program" msgstr "Startprogramm" #: ../lib/modules/windowsUser.inc:153 ../lib/modules/windowsUser.inc:448 -#: ../lib/modules/windowsUser.inc:844 ../lib/modules/windowsUser.inc:1129 -#: ../lib/modules/windowsUser.inc:2718 ../lib/modules/inetOrgPerson.inc:164 +#: ../lib/modules/windowsUser.inc:844 ../lib/modules/windowsUser.inc:1128 +#: ../lib/modules/windowsUser.inc:2690 ../lib/modules/inetOrgPerson.inc:164 #: ../lib/modules/inetOrgPerson.inc:251 ../lib/modules/inetOrgPerson.inc:558 #: ../lib/modules/inetOrgPerson.inc:778 ../lib/modules/inetOrgPerson.inc:1209 -#: ../lib/modules/inetOrgPerson.inc:1212 ../lib/modules/inetOrgPerson.inc:1975 -#: ../lib/modules/inetOrgPerson.inc:2000 ../lib/modules/inetOrgPerson.inc:2794 -#: ../lib/modules/inetOrgPerson.inc:3883 ../lib/modules/inetOrgPerson.inc:3916 +#: ../lib/modules/inetOrgPerson.inc:1212 ../lib/modules/inetOrgPerson.inc:1976 +#: ../lib/modules/inetOrgPerson.inc:2001 ../lib/modules/inetOrgPerson.inc:2795 +#: ../lib/modules/inetOrgPerson.inc:3884 ../lib/modules/inetOrgPerson.inc:3917 msgid "Initials" msgstr "Initialen" @@ -7143,11 +7146,11 @@ msgstr "" msgid "Inode soft quota must be smaller than inode hard quota." msgstr "Das weiche Inode-Quota muss kleiner sein als das harte Inode-Quota." -#: ../templates/selfService/adminMain.php:692 +#: ../templates/selfService/adminMain.php:704 msgid "Input field" msgstr "Eingabefeld" -#: ../templates/selfService/adminMain.php:595 +#: ../templates/selfService/adminMain.php:607 msgid "Input fields" msgstr "Eingabefelder" @@ -7155,8 +7158,8 @@ msgstr "Eingabefelder" #: ../lib/modules/asteriskAccount.inc:275 #: ../lib/modules/asteriskAccount.inc:389 #: ../lib/modules/asteriskAccount.inc:650 -#: ../lib/modules/asteriskAccount.inc:992 -#: ../lib/modules/asteriskAccount.inc:1236 +#: ../lib/modules/asteriskAccount.inc:993 +#: ../lib/modules/asteriskAccount.inc:1237 msgid "Insecure" msgstr "Unsicher" @@ -7172,7 +7175,7 @@ msgstr "Benutzer- oder Gruppenname wurde in Anmeldeskript eingefügt." msgid "Inserted user or group name in profile path." msgstr "Benutzer- oder Gruppenname wurde in Profilpfad eingefügt." -#: ../lib/modules/autoDelete.inc:360 +#: ../lib/modules/autoDelete.inc:370 msgid "Insufficient rights for this operation." msgstr "Ihre Rechte sind für diese Operation nicht ausreichend." @@ -7188,7 +7191,7 @@ msgstr "Ungültiges RDN-Attribut!" msgid "Invalid RDN value" msgstr "Ungültiger RDN Wert" -#: ../lib/modules/posixAccount.inc:1744 ../lib/modules/yubiKeyUser.inc:143 +#: ../lib/modules/posixAccount.inc:1723 ../lib/modules/yubiKeyUser.inc:147 msgid "" "Invalid configuration detected. Please edit your server profile (module " "settings) and fill all required fields." @@ -7227,8 +7230,8 @@ msgstr "Ungültige Option" msgid "Invalid password for IMAP admin or other problem occured." msgstr "Das IMAP-Passwort ist falsch oder anderer Fehler ist aufgetreten." -#: ../lib/modules/ldapPublicKey.inc:653 ../lib/modules/yubiKeyUser.inc:533 -#: ../lib/modules/inetOrgPerson.inc:3668 ../lib/modules/customFields.inc:1577 +#: ../lib/modules/ldapPublicKey.inc:651 ../lib/modules/yubiKeyUser.inc:530 +#: ../lib/modules/inetOrgPerson.inc:3669 ../lib/modules/customFields.inc:1577 msgid "Invalid request" msgstr "Ungültige Anfrage" @@ -7237,8 +7240,8 @@ msgid "Invalid server name. Please enter \"server\" or \"server:port\"." msgstr "" "Ungültiger Servername, bitte geben Sie \"server\" oder \"server:port\" ein." -#: ../lib/modules/passwordSelfReset.inc:1125 -#: ../lib/modules/passwordSelfReset.inc:1376 +#: ../lib/modules/passwordSelfReset.inc:1123 +#: ../lib/modules/passwordSelfReset.inc:1374 #, php-format msgid "Invalid value in field \"%s\"." msgstr "Ungültiger Wert im Feld \"%s\"." @@ -7258,7 +7261,7 @@ msgstr "Liste der Einladungsregeln" msgid "It is not possible to delete all ranges." msgstr "Sie können nicht alle Adressbereiche löschen." -#: ../lib/modules/posixGroup.inc:753 ../lib/modules/posixAccount.inc:95 +#: ../lib/modules/posixGroup.inc:702 ../lib/modules/posixAccount.inc:95 msgid "" "It is possible that this ID-number is reused. This can cause several " "problems because files with old permissions might still exist. To avoid this " @@ -7274,27 +7277,27 @@ msgid "Japanese" msgstr "Japanisch" #: ../templates/config/jobList.php:117 ../templates/config/jobs.php:276 -#: ../lib/modules/pykotaUser.inc:252 ../lib/modules/pykotaUser.inc:351 -#: ../lib/modules/pykotaUser.inc:1029 +#: ../lib/modules/pykotaUser.inc:252 ../lib/modules/pykotaUser.inc:339 +#: ../lib/modules/pykotaUser.inc:1011 msgid "Job history" msgstr "Jobhistorie" #: ../lib/modules/pykotaUser.inc:159 ../lib/modules/pykotaUser.inc:257 -#: ../lib/modules/pykotaUser.inc:290 ../lib/modules/pykotaUser.inc:1200 +#: ../lib/modules/pykotaUser.inc:290 ../lib/modules/pykotaUser.inc:1182 msgid "Job suffix" msgstr "Job-Suffix" #: ../lib/types/user.inc:117 ../lib/modules/windowsUser.inc:302 #: ../lib/modules/windowsUser.inc:682 ../lib/modules/windowsUser.inc:788 #: ../lib/modules/windowsUser.inc:892 ../lib/modules/windowsUser.inc:1035 -#: ../lib/modules/windowsUser.inc:1169 ../lib/modules/windowsUser.inc:2741 -#: ../lib/modules/windowsUser.inc:3641 ../lib/modules/inetOrgPerson.inc:94 +#: ../lib/modules/windowsUser.inc:1168 ../lib/modules/windowsUser.inc:2713 +#: ../lib/modules/windowsUser.inc:3613 ../lib/modules/inetOrgPerson.inc:94 #: ../lib/modules/inetOrgPerson.inc:164 ../lib/modules/inetOrgPerson.inc:267 #: ../lib/modules/inetOrgPerson.inc:531 ../lib/modules/inetOrgPerson.inc:570 #: ../lib/modules/inetOrgPerson.inc:574 ../lib/modules/inetOrgPerson.inc:1451 -#: ../lib/modules/inetOrgPerson.inc:1454 ../lib/modules/inetOrgPerson.inc:1946 -#: ../lib/modules/inetOrgPerson.inc:2054 ../lib/modules/inetOrgPerson.inc:2805 -#: ../lib/modules/inetOrgPerson.inc:3874 ../lib/modules/inetOrgPerson.inc:3911 +#: ../lib/modules/inetOrgPerson.inc:1454 ../lib/modules/inetOrgPerson.inc:1947 +#: ../lib/modules/inetOrgPerson.inc:2055 ../lib/modules/inetOrgPerson.inc:2806 +#: ../lib/modules/inetOrgPerson.inc:3875 ../lib/modules/inetOrgPerson.inc:3912 msgid "Job title" msgstr "Berufsbezeichnung" @@ -7307,11 +7310,11 @@ msgstr "Berufsbezeichnung des Benutzers: Präsident, Abteilungsleiter, ..." msgid "Jobs" msgstr "Jobs" -#: ../lib/lists.inc:328 +#: ../lib/lists.inc:324 msgid "Jump 10 pages backward" msgstr "Springe 10 Seiten zurück" -#: ../lib/lists.inc:361 +#: ../lib/lists.inc:357 msgid "Jump 10 pages forward" msgstr "Springe 10 Seiten vorwärts" @@ -7327,15 +7330,15 @@ msgstr "Wechseln des Attributtyps" msgid "Jump to an object class" msgstr "Wechseln der Objektklasse" -#: ../lib/lists.inc:320 +#: ../lib/lists.inc:316 msgid "Jump to first page" msgstr "Springe zur ersten Seite" -#: ../lib/lists.inc:369 +#: ../lib/lists.inc:365 msgid "Jump to last page" msgstr "Springe zur letzten Seite" -#: ../lib/modules/posixAccount.inc:337 ../lib/modules/inetOrgPerson.inc:755 +#: ../lib/modules/posixAccount.inc:342 ../lib/modules/inetOrgPerson.inc:755 msgid "K5KEY is only needed if you use Kerberos with smbk5pwd." msgstr "" "K5KEY wird nur benötigt wenn Sie Kerberos in Verbindung mit smbk5pwd " @@ -7361,7 +7364,7 @@ msgstr "Kolab gemeinsame Ordner" msgid "Kolab shared folders (e.g. mail folders)" msgstr "Kolab gemeinsame Ordner (z.B. email-Ordner)" -#: ../lib/modules/kopanoServer.inc:56 ../lib/modules/kopanoGroup.inc:62 +#: ../lib/modules/kopanoServer.inc:55 ../lib/modules/kopanoGroup.inc:62 #: ../lib/modules/kopanoUser.inc:79 msgid "Kopano" msgstr "Kopano" @@ -7388,12 +7391,12 @@ msgstr "Kopano Dynamische Gruppe" msgid "Kopano dynamic groups" msgstr "Kopano Dynamische Gruppen" -#: ../lib/modules/kopanoServer.inc:92 ../lib/modules/kopanoServer.inc:388 +#: ../lib/modules/kopanoServer.inc:91 ../lib/modules/kopanoServer.inc:368 #: ../lib/modules/kopanoGroup.inc:118 ../lib/modules/kopanoGroup.inc:705 -#: ../lib/modules/kopanoContact.inc:109 ../lib/modules/kopanoContact.inc:807 +#: ../lib/modules/kopanoContact.inc:109 ../lib/modules/kopanoContact.inc:804 #: ../lib/modules/kopanoDynamicGroup.inc:98 #: ../lib/modules/kopanoDynamicGroup.inc:432 ../lib/modules/kopanoUser.inc:189 -#: ../lib/modules/kopanoUser.inc:420 ../lib/modules/kopanoUser.inc:1811 +#: ../lib/modules/kopanoUser.inc:420 ../lib/modules/kopanoUser.inc:1730 #: ../lib/modules/kopanoAddressList.inc:82 #: ../lib/modules/kopanoAddressList.inc:357 msgid "Kopano schema" @@ -7407,7 +7410,7 @@ msgstr "Kopano speichert die Archive des Benutzers auf diesen Servern." msgid "Korean" msgstr "Koreanisch" -#: ../lib/modules/posixGroup.inc:506 +#: ../lib/modules/posixGroup.inc:455 msgid "" "LAM checks if the entered group name and GID are unique. Here you can enter " "the LDAP suffix that is used to search for duplicates. By default the " @@ -7421,7 +7424,7 @@ msgstr "" "dies nur ändern falls Sie mehrere Serverprofile mit unterschiedlichen OUs " "verwenden und eindeutige Gruppennamen und GIDs benötigen." -#: ../lib/modules/posixAccount.inc:373 +#: ../lib/modules/posixAccount.inc:378 msgid "" "LAM checks if the entered user name and UID are unique. Here you can enter " "the LDAP suffix that is used to search for duplicates. By default the " @@ -7443,7 +7446,7 @@ msgstr "LAM-Einstellungen" msgid "LAM has checked your input and is now ready to create the accounts." msgstr "LAM hat Ihre Eingaben überprüft und kann die Accounts nun erstellen." -#: ../lib/modules/posixGroup.inc:494 ../lib/modules/posixAccount.inc:336 +#: ../lib/modules/posixGroup.inc:443 ../lib/modules/posixAccount.inc:341 #: ../lib/modules/inetOrgPerson.inc:754 ../lib/modules/customFields.inc:150 msgid "" "LAM supports CRYPT, CRYPT-SHA512, SHA, SSHA, MD5 and SMD5 to generate the " @@ -7469,7 +7472,7 @@ msgstr "LAM-Benutzerpasswort" msgid "LAM was unable to create account %s! An LDAP error occured." msgstr "LAM konnte den Account %s nicht anlegen! Ein LDAP-Fehler trat auf." -#: ../lib/modules/sambaGroupMapping.inc:577 +#: ../lib/modules/sambaGroupMapping.inc:574 msgid "LAM was unable to find a Samba 3 domain with this name!" msgstr "LAM konnte keine Samba 3 Domäne mit diesem Namen finden!" @@ -7477,12 +7480,12 @@ msgstr "LAM konnte keine Samba 3 Domäne mit diesem Namen finden!" msgid "LAM was unable to find a domain with this name!" msgstr "LAM konnte keine Domäne mit diesem Namen finden!" -#: ../lib/modules/posixAccount.inc:113 ../lib/modules/windowsUser.inc:2516 +#: ../lib/modules/posixAccount.inc:113 ../lib/modules/windowsUser.inc:2488 msgid "LAM was unable to find a group with this name!" msgstr "LAM konnte keine Gruppe mit diesem Namen finden!" -#: ../lib/modules/posixAccount.inc:2899 ../lib/modules/posixAccount.inc:2980 -#: ../lib/modules/windowsUser.inc:2661 ../lib/modules/nisNetGroupUser.inc:580 +#: ../lib/modules/posixAccount.inc:2857 ../lib/modules/posixAccount.inc:2939 +#: ../lib/modules/windowsUser.inc:2633 ../lib/modules/nisNetGroupUser.inc:580 #: ../lib/modules/groupOfNamesUser.inc:449 #, php-format msgid "LAM was unable to modify group memberships for group: %s" @@ -7493,7 +7496,7 @@ msgstr "LAM konnte die Gruppenzugehörigkeiten für Gruppe %s nicht ändern." msgid "LAM was unable to modify memberships for role: %s" msgstr "LAM konnte die Rollenzugehörigkeiten für Rolle %s nicht ändern." -#: ../lib/modules/posixGroup.inc:510 ../lib/modules/posixAccount.inc:381 +#: ../lib/modules/posixGroup.inc:459 ../lib/modules/posixAccount.inc:386 msgid "" "LAM will automatically suggest UID/GID numbers. You can either use a fixed " "range of numbers or an LDAP entry with object class \"sambaUnixIdPool\" or " @@ -7503,11 +7506,11 @@ msgstr "" "festen Nummernkreis festlegen oder einen LDAP-Eintrag mit der Objektklasse " "\"sambaUnixIdPool\" oder \"msSFU30DomainInfo\" verwenden." -#: ../help/help.inc:273 +#: ../help/help.inc:275 msgid "LAM will search for accounts in this part of the LDAP tree." msgstr "LAM wird innerhalb dieses LDAP-Astes nach Objekten suchen." -#: ../lib/modules/posixAccount.inc:315 +#: ../lib/modules/posixAccount.inc:320 msgid "" "LAM will suggest a user name based on e.g. first and last name. Here you can " "specify the suggestion. %sn% will be replaced by the last name. @givenname@ " @@ -7519,7 +7522,7 @@ msgstr "" "@givenname@ wird durch den ersten Buchstaben des Vornamens ersetzt. Es " "können nur Attribute des Tabs Persönlich verwendet werden." -#: ../help/help.inc:275 +#: ../help/help.inc:277 msgid "" "LAM will use this LDAP DN and password to search for accounts. It is " "sufficient to specify an account with read rights. If nothing is inserted " @@ -7538,7 +7541,7 @@ msgid "LDAP + program" msgstr "LDAP + Programm" #: ../templates/config/confmodules.php:113 ../templates/config/jobList.php:102 -#: ../templates/config/jobs.php:93 ../templates/config/confmain.php:154 +#: ../templates/config/jobs.php:93 ../templates/config/confmain.php:158 #: ../templates/config/moduleSettings.php:108 #: ../templates/config/conftypes.php:139 msgid "LDAP Account Manager Configuration" @@ -7548,7 +7551,7 @@ msgstr "LDAP Account Manager Einstellungen" msgid "LDAP DN of default PPolicy password policy entry." msgstr "Die LDAP-DN der standardmäßigen PPolicy Passwortrichtlinie." -#: ../templates/tools/multiEdit.php:223 ../templates/config/conftypes.php:373 +#: ../templates/tools/multiEdit.php:225 ../templates/config/conftypes.php:373 #: ../lib/modules/customFields.inc:4127 msgid "LDAP Suffix is invalid!" msgstr "LDAP-Suffix ist ungültig!" @@ -7587,13 +7590,13 @@ msgstr "LDAP-Einträge, die Mitglieder dieser Liste sind." msgid "LDAP entries that moderate this list (e.g. approve mails)." msgstr "LDAP-Einträge, die diese Liste moderieren (z.B. Emails genehmigen)." -#: ../templates/login.php:593 ../lib/account.inc:1462 +#: ../templates/login.php:593 ../lib/account.inc:1477 msgid "LDAP error, server says:" msgstr "LDAP-Fehler, der Server meldet:" -#: ../templates/tools/multiEdit.php:137 ../templates/config/confmain.php:444 +#: ../templates/tools/multiEdit.php:139 ../templates/config/confmain.php:448 #: ../lib/modules/customFields.inc:197 ../lib/modules/customFields.inc:4095 -#: ../help/help.inc:353 +#: ../help/help.inc:359 msgid "LDAP filter" msgstr "LDAP-Filter" @@ -7617,11 +7620,11 @@ msgstr "" msgid "LDAP import/export" msgstr "LDIF Import/Export" -#: ../lib/import.inc:573 ../lib/modules.inc:1411 +#: ../lib/import.inc:573 ../lib/modules.inc:1417 msgid "LDAP operation successful." msgstr "LDAP-Operation war erfolgreich." -#: ../templates/selfService/adminMain.php:432 +#: ../templates/selfService/adminMain.php:438 msgid "LDAP password" msgstr "LDAP-Passwort" @@ -7629,19 +7632,19 @@ msgstr "LDAP-Passwort" msgid "LDAP said" msgstr "LDAP meldet" -#: ../templates/config/confmain.php:421 ../help/help.inc:128 +#: ../templates/config/confmain.php:425 ../help/help.inc:128 msgid "LDAP search" msgstr "LDAP-Suche" -#: ../templates/selfService/adminMain.php:427 ../help/help.inc:276 +#: ../templates/selfService/adminMain.php:433 ../help/help.inc:278 msgid "LDAP search attribute" msgstr "LDAP-Suchattribut" -#: ../lib/account.inc:998 +#: ../lib/account.inc:1010 msgid "LDAP search failed! Please check your preferences." msgstr "LDAP-Suche fehlgeschlagen! Bitte überprüfen Sie die Einstellungen." -#: ../templates/config/confmain.php:204 ../help/help.inc:130 +#: ../templates/config/confmain.php:208 ../help/help.inc:130 msgid "LDAP search limit" msgstr "LDAP-Suchlimit" @@ -7653,18 +7656,18 @@ msgstr "Auswahlliste mit LDAP-Suche" msgid "LDAP server" msgstr "LDAP-Server" -#: ../lib/account.inc:989 +#: ../lib/account.inc:1001 msgid "LDAP sizelimit exceeded, not all entries are shown." msgstr "" "LDAP Größenbeschränkung überschritten, es werden nicht alle Einträge " "angezeigt." -#: ../templates/tools/multiEdit.php:127 ../templates/config/confmain.php:440 +#: ../templates/tools/multiEdit.php:129 ../templates/config/confmain.php:444 #: ../templates/config/conftypes.php:221 -#: ../templates/selfService/adminMain.php:418 +#: ../templates/selfService/adminMain.php:424 #: ../templates/profedit/profilepage.php:205 #: ../lib/modules/customFields.inc:193 ../lib/modules/customFields.inc:4092 -#: ../help/help.inc:75 ../help/help.inc:272 ../help/help.inc:351 +#: ../help/help.inc:75 ../help/help.inc:274 ../help/help.inc:357 msgid "LDAP suffix" msgstr "LDAP-Suffix" @@ -7672,11 +7675,11 @@ msgstr "LDAP-Suffix" msgid "LDAP upload in progress. Please wait." msgstr "LDAP-Upload läuft. Bitte warten." -#: ../templates/selfService/adminMain.php:430 +#: ../templates/selfService/adminMain.php:436 msgid "LDAP user" msgstr "LDAP-Benutzer" -#: ../help/help.inc:274 +#: ../help/help.inc:276 msgid "LDAP user and password" msgstr "LDAP-Benutzer und Passwort" @@ -7692,11 +7695,11 @@ msgstr "LDAP-Ansichten mit nsview" msgid "LDIF Export" msgstr "LDIF-Export" -#: ../templates/tools/importexport.php:170 ../help/help.inc:359 +#: ../templates/tools/importexport.php:170 ../help/help.inc:365 msgid "LDIF data" msgstr "LDIF-Daten" -#: ../templates/tools/multiEdit.php:468 +#: ../templates/tools/multiEdit.php:470 msgid "LDIF file" msgstr "LDIF-Datei" @@ -7709,15 +7712,15 @@ msgstr "LDIF-Import" msgid "LDIF import only supports version 1" msgstr "Der LDIF-Import unterstützt nur Version 1." -#: ../templates/config/confmain.php:493 -#: ../templates/selfService/adminMain.php:513 +#: ../templates/config/confmain.php:508 +#: ../templates/selfService/adminMain.php:525 #: ../lib/modules/customFields.inc:93 ../lib/modules/customFields.inc:653 #: ../lib/modules/customFields.inc:1211 ../lib/modules/customFields.inc:1964 -#: ../lib/modules/customFields.inc:3640 ../help/help.inc:304 +#: ../lib/modules/customFields.inc:3640 ../help/help.inc:308 msgid "Label" msgstr "Beschriftung" -#: ../templates/tests/lamdaemonTest.php:203 +#: ../templates/tests/lamdaemonTest.php:204 msgid "" "Lamdaemon path does not end with \".pl\". Did you enter the full path to the " "script?" @@ -7725,11 +7728,11 @@ msgstr "" "Der Pfad für das lamdaemon-Skript endet nicht auf \".pl\". Haben Sie den " "vollen Pfad zum Skript angegeben?" -#: ../templates/tests/lamdaemonTest.php:191 +#: ../templates/tests/lamdaemonTest.php:192 msgid "Lamdaemon server and path" msgstr "Lamdaemon Server und Pfad" -#: ../templates/config/confmain.php:277 +#: ../templates/config/confmain.php:281 msgid "Lamdaemon settings" msgstr "Lamdaemon-Einstellungen" @@ -7737,7 +7740,7 @@ msgstr "Lamdaemon-Einstellungen" msgid "Lamdaemon successfully run." msgstr "Lamdaemon konnte erfolgreich ausgeführt werden." -#: ../templates/tests/index.php:57 ../templates/tests/lamdaemonTest.php:65 +#: ../templates/tests/index.php:57 ../templates/tests/lamdaemonTest.php:67 #: ../lib/tools/tests.inc:111 msgid "Lamdaemon test" msgstr "Lamdaemon-Test" @@ -7762,36 +7765,36 @@ msgstr "Lamdaemon: NSS LDAP prüfen" msgid "Lamdaemon: read quotas" msgstr "Lamdaemon: Quotas lesen" -#: ../templates/login.php:374 ../templates/selfService/selfServiceLogin.php:307 +#: ../templates/login.php:374 ../templates/selfService/selfServiceLogin.php:308 msgid "Language" msgstr "Sprache" -#: ../templates/config/confmain.php:650 +#: ../templates/config/confmain.php:665 msgid "Language is not defined!" msgstr "Sprache ist nicht definiert!" -#: ../templates/config/confmain.php:242 +#: ../templates/config/confmain.php:246 msgid "Language settings" msgstr "Spacheinstellungen" #: ../lib/modules/mitKerberos.inc:143 ../lib/modules/mitKerberos.inc:255 -#: ../lib/modules/mitKerberos.inc:395 ../lib/modules/mitKerberos.inc:808 +#: ../lib/modules/mitKerberos.inc:399 ../lib/modules/mitKerberos.inc:830 #: ../lib/modules/windowsUser.inc:282 ../lib/modules/windowsUser.inc:889 -#: ../lib/modules/windowsUser.inc:1242 ../lib/modules/windowsUser.inc:2791 -#: ../lib/modules/windowsUser.inc:3639 +#: ../lib/modules/windowsUser.inc:1246 ../lib/modules/windowsUser.inc:2763 +#: ../lib/modules/windowsUser.inc:3611 msgid "Last login" msgstr "Letzte Anmeldung" #: ../lib/types/user.inc:114 ../lib/modules/selfRegistration.inc:83 #: ../lib/modules/selfRegistration.inc:206 ../lib/modules/windowsUser.inc:185 #: ../lib/modules/windowsUser.inc:430 ../lib/modules/windowsUser.inc:852 -#: ../lib/modules/windowsUser.inc:996 ../lib/modules/windowsUser.inc:1126 -#: ../lib/modules/windowsUser.inc:2726 ../lib/modules/inetOrgPerson.inc:70 +#: ../lib/modules/windowsUser.inc:996 ../lib/modules/windowsUser.inc:1125 +#: ../lib/modules/windowsUser.inc:2698 ../lib/modules/inetOrgPerson.inc:70 #: ../lib/modules/inetOrgPerson.inc:158 ../lib/modules/inetOrgPerson.inc:242 #: ../lib/modules/inetOrgPerson.inc:477 ../lib/modules/inetOrgPerson.inc:582 #: ../lib/modules/inetOrgPerson.inc:1201 ../lib/modules/inetOrgPerson.inc:1204 -#: ../lib/modules/inetOrgPerson.inc:1948 ../lib/modules/inetOrgPerson.inc:2559 -#: ../lib/modules/inetOrgPerson.inc:3903 +#: ../lib/modules/inetOrgPerson.inc:1949 ../lib/modules/inetOrgPerson.inc:2560 +#: ../lib/modules/inetOrgPerson.inc:3904 msgid "Last name" msgstr "Nachname" @@ -7806,18 +7809,18 @@ msgstr "" "Nachname des Benutzers. Es sind nur Buchstaben, - und Leerzeichen erlaubt." #: ../lib/modules/mitKerberos.inc:139 ../lib/modules/mitKerberos.inc:254 -#: ../lib/modules/mitKerberos.inc:387 ../lib/modules/mitKerberos.inc:804 +#: ../lib/modules/mitKerberos.inc:389 ../lib/modules/mitKerberos.inc:826 #: ../lib/modules/sambaSamAccount.inc:398 -#: ../lib/modules/sambaSamAccount.inc:1115 -#: ../lib/modules/sambaSamAccount.inc:1907 -#: ../lib/modules/sambaSamAccount.inc:2323 ../lib/modules/ppolicyUser.inc:91 +#: ../lib/modules/sambaSamAccount.inc:1147 +#: ../lib/modules/sambaSamAccount.inc:1954 +#: ../lib/modules/sambaSamAccount.inc:2370 ../lib/modules/ppolicyUser.inc:91 #: ../lib/modules/ppolicyUser.inc:140 ../lib/modules/ppolicyUser.inc:389 #: ../lib/modules/windowsUser.inc:278 ../lib/modules/windowsUser.inc:886 -#: ../lib/modules/windowsUser.inc:1236 ../lib/modules/windowsUser.inc:2789 -#: ../lib/modules/windowsUser.inc:2942 ../lib/modules/windowsUser.inc:3638 +#: ../lib/modules/windowsUser.inc:1237 ../lib/modules/windowsUser.inc:2761 +#: ../lib/modules/windowsUser.inc:2914 ../lib/modules/windowsUser.inc:3610 #: ../lib/modules/shadowAccount.inc:168 ../lib/modules/shadowAccount.inc:202 -#: ../lib/modules/shadowAccount.inc:369 ../lib/modules/shadowAccount.inc:534 -#: ../lib/modules/shadowAccount.inc:570 ../lib/modules/shadowAccount.inc:758 +#: ../lib/modules/shadowAccount.inc:371 ../lib/modules/shadowAccount.inc:554 +#: ../lib/modules/shadowAccount.inc:597 ../lib/modules/shadowAccount.inc:785 msgid "Last password change" msgstr "Letzte Passwortänderung" @@ -7830,8 +7833,8 @@ msgstr "Letzte Passwortänderung (nur lesen)" #: ../lib/modules/asteriskAccount.inc:296 #: ../lib/modules/asteriskAccount.inc:542 #: ../lib/modules/asteriskAccount.inc:756 -#: ../lib/modules/asteriskAccount.inc:1013 -#: ../lib/modules/asteriskAccount.inc:1257 +#: ../lib/modules/asteriskAccount.inc:1014 +#: ../lib/modules/asteriskAccount.inc:1258 msgid "Last qualify milliseconds" msgstr "Millisekunden seit letzter Verbindung" @@ -7852,10 +7855,10 @@ msgstr "Lizenzdaten" #: ../lib/modules/pykotaGroup.inc:119 ../lib/modules/pykotaGroup.inc:129 #: ../lib/modules/pykotaGroup.inc:150 ../lib/modules/pykotaGroup.inc:159 -#: ../lib/modules/pykotaGroup.inc:199 ../lib/modules/pykotaGroup.inc:496 +#: ../lib/modules/pykotaGroup.inc:199 ../lib/modules/pykotaGroup.inc:495 #: ../lib/modules/pykotaUser.inc:131 ../lib/modules/pykotaUser.inc:172 #: ../lib/modules/pykotaUser.inc:210 ../lib/modules/pykotaUser.inc:238 -#: ../lib/modules/pykotaUser.inc:361 ../lib/modules/pykotaUser.inc:914 +#: ../lib/modules/pykotaUser.inc:349 ../lib/modules/pykotaUser.inc:896 msgid "Limit type" msgstr "Limittyp" @@ -7870,11 +7873,11 @@ msgstr "Zeilenenden" #: ../lib/modules/selfRegistration.inc:56 #: ../lib/modules/selfRegistration.inc:190 #: ../lib/modules/passwordSelfReset.inc:103 -#: ../lib/modules/passwordSelfReset.inc:644 +#: ../lib/modules/passwordSelfReset.inc:642 msgid "Link text" msgstr "Linktext" -#: ../templates/selfService/adminMain.php:359 +#: ../templates/selfService/adminMain.php:365 msgid "Link to self service login page for your users" msgstr "Link zum SelfService für Ihre Benutzer" @@ -7921,7 +7924,7 @@ msgstr "" "Liste der Samba-Arbeitsstationen, auf welchen sich der Benutzer anmelden " "darf. Leer heißt jede Arbeitsstation." -#: ../templates/config/confmain.php:644 +#: ../templates/config/confmain.php:659 msgid "List of admin users is empty or invalid!" msgstr "Liste der berechtigten Benutzer ist ungültig!" @@ -7938,7 +7941,7 @@ msgstr "Liste der unerlaubten Codecs." msgid "List of entries to be deleted:" msgstr "Liste der zu löschenden Einträge:" -#: ../templates/config/confmain.php:436 +#: ../templates/config/confmain.php:440 msgid "List of valid users" msgstr "Liste der berechtigten Benutzer" @@ -7946,7 +7949,7 @@ msgstr "Liste der berechtigten Benutzer" msgid "Listeners" msgstr "Listener" -#: ../lib/modules.inc:1327 ../help/help.inc:250 +#: ../lib/modules.inc:1333 ../help/help.inc:252 msgid "Load profile" msgstr "Profil laden" @@ -7999,29 +8002,29 @@ msgstr "Lokale Gruppe" #: ../lib/modules/sambaGroupMapping.inc:115 #: ../lib/modules/sambaGroupMapping.inc:166 -#: ../lib/modules/sambaGroupMapping.inc:363 -#: ../lib/modules/sambaGroupMapping.inc:531 +#: ../lib/modules/sambaGroupMapping.inc:369 +#: ../lib/modules/sambaGroupMapping.inc:528 msgid "Local members" msgstr "Lokale Mitglieder" #: ../lib/types/host.inc:101 ../lib/modules/ipHost.inc:73 #: ../lib/modules/ipHost.inc:97 ../lib/modules/ipHost.inc:113 #: ../lib/modules/ipHost.inc:140 ../lib/modules/ipHost.inc:299 -#: ../lib/modules/windowsHost.inc:82 ../lib/modules/windowsHost.inc:106 -#: ../lib/modules/windowsHost.inc:121 ../lib/modules/windowsHost.inc:144 -#: ../lib/modules/windowsHost.inc:286 ../lib/modules/device.inc:80 +#: ../lib/modules/windowsHost.inc:81 ../lib/modules/windowsHost.inc:105 +#: ../lib/modules/windowsHost.inc:120 ../lib/modules/windowsHost.inc:143 +#: ../lib/modules/windowsHost.inc:289 ../lib/modules/device.inc:80 #: ../lib/modules/device.inc:103 ../lib/modules/device.inc:116 #: ../lib/modules/device.inc:164 ../lib/modules/device.inc:426 #: ../lib/modules/windowsUser.inc:157 ../lib/modules/windowsUser.inc:478 #: ../lib/modules/windowsUser.inc:845 ../lib/modules/windowsUser.inc:948 -#: ../lib/modules/windowsUser.inc:1136 ../lib/modules/windowsUser.inc:2719 -#: ../lib/modules/windowsUser.inc:2935 ../lib/modules/inetOrgPerson.inc:163 +#: ../lib/modules/windowsUser.inc:1135 ../lib/modules/windowsUser.inc:2691 +#: ../lib/modules/windowsUser.inc:2907 ../lib/modules/inetOrgPerson.inc:163 #: ../lib/modules/inetOrgPerson.inc:445 ../lib/modules/inetOrgPerson.inc:492 #: ../lib/modules/inetOrgPerson.inc:713 ../lib/modules/inetOrgPerson.inc:717 #: ../lib/modules/inetOrgPerson.inc:1272 ../lib/modules/inetOrgPerson.inc:1275 -#: ../lib/modules/inetOrgPerson.inc:1971 ../lib/modules/inetOrgPerson.inc:2015 -#: ../lib/modules/inetOrgPerson.inc:2713 ../lib/modules/inetOrgPerson.inc:3862 -#: ../lib/modules/inetOrgPerson.inc:3905 +#: ../lib/modules/inetOrgPerson.inc:1972 ../lib/modules/inetOrgPerson.inc:2016 +#: ../lib/modules/inetOrgPerson.inc:2714 ../lib/modules/inetOrgPerson.inc:3863 +#: ../lib/modules/inetOrgPerson.inc:3906 msgid "Location" msgstr "Ort" @@ -8029,7 +8032,7 @@ msgstr "Ort" msgid "Location where new alias is stored." msgstr "Lageort für den neuen Alias." -#: ../lib/types/user.inc:437 ../lib/types/user.inc:442 +#: ../lib/types/user.inc:438 ../lib/types/user.inc:443 msgid "Lock" msgstr "Sperren" @@ -8043,32 +8046,32 @@ msgstr "Account sperren" msgid "Lock account?" msgstr "Account sperren?" -#: ../lib/modules/posixGroup.inc:239 ../lib/modules/posixAccount.inc:1706 -#: ../lib/modules/posixAccount.inc:2447 ../lib/modules/inetOrgPerson.inc:1565 +#: ../lib/modules/posixGroup.inc:222 ../lib/modules/posixAccount.inc:1687 +#: ../lib/modules/posixAccount.inc:2405 ../lib/modules/inetOrgPerson.inc:1565 msgid "Lock password" msgstr "Passwort deaktivieren" -#: ../lib/types/user.inc:333 ../lib/types/user.inc:891 -#: ../lib/types/user.inc:1120 +#: ../lib/types/user.inc:334 ../lib/types/user.inc:892 +#: ../lib/types/user.inc:1121 msgid "Locked" msgstr "Gesperrt" -#: ../templates/lists/changePassword.php:424 ../lib/types/user.inc:329 -#: ../lib/types/user.inc:519 ../lib/types/user.inc:1116 -#: ../lib/modules/locking389ds.inc:93 +#: ../templates/lists/changePassword.php:424 ../lib/types/user.inc:330 +#: ../lib/types/user.inc:520 ../lib/types/user.inc:1117 +#: ../lib/modules/locking389ds.inc:94 msgid "Locked till" msgstr "Gesperrt bis" -#: ../lib/types/ppolicyType.inc:86 ../lib/modules/ppolicy.inc:104 -#: ../lib/modules/ppolicy.inc:151 ../lib/modules/ppolicy.inc:171 -#: ../lib/modules/ppolicy.inc:261 ../lib/modules/ppolicy.inc:475 -#: ../lib/modules/sambaDomain.inc:133 ../lib/modules/sambaDomain.inc:198 -#: ../lib/modules/sambaDomain.inc:223 ../lib/modules/sambaDomain.inc:340 -#: ../lib/modules/sambaDomain.inc:604 +#: ../lib/types/ppolicyType.inc:86 ../lib/modules/ppolicy.inc:103 +#: ../lib/modules/ppolicy.inc:150 ../lib/modules/ppolicy.inc:170 +#: ../lib/modules/ppolicy.inc:254 ../lib/modules/ppolicy.inc:468 +#: ../lib/modules/sambaDomain.inc:132 ../lib/modules/sambaDomain.inc:197 +#: ../lib/modules/sambaDomain.inc:222 ../lib/modules/sambaDomain.inc:333 +#: ../lib/modules/sambaDomain.inc:601 msgid "Lockout duration" msgstr "Sperrdauer" -#: ../lib/modules/sambaDomain.inc:223 +#: ../lib/modules/sambaDomain.inc:222 msgid "Lockout duration must be are natural number." msgstr "Sperrdauer muss eine natürliche Zahl sein." @@ -8076,19 +8079,19 @@ msgstr "Sperrdauer muss eine natürliche Zahl sein." msgid "Lockout time" msgstr "Sperrdauer" -#: ../lib/types/ppolicyType.inc:91 ../lib/modules/ppolicy.inc:124 -#: ../lib/modules/ppolicy.inc:150 ../lib/modules/ppolicy.inc:255 -#: ../lib/modules/ppolicy.inc:494 +#: ../lib/types/ppolicyType.inc:91 ../lib/modules/ppolicy.inc:123 +#: ../lib/modules/ppolicy.inc:149 ../lib/modules/ppolicy.inc:248 +#: ../lib/modules/ppolicy.inc:487 msgid "Lockout users" msgstr "Benutzer sperren" -#: ../lib/modules/sambaDomain.inc:121 ../lib/modules/sambaDomain.inc:195 -#: ../lib/modules/sambaDomain.inc:225 ../lib/modules/sambaDomain.inc:316 -#: ../lib/modules/sambaDomain.inc:601 +#: ../lib/modules/sambaDomain.inc:120 ../lib/modules/sambaDomain.inc:194 +#: ../lib/modules/sambaDomain.inc:224 ../lib/modules/sambaDomain.inc:309 +#: ../lib/modules/sambaDomain.inc:598 msgid "Lockout users after bad logon attempts" msgstr "Kontosperre bei fehlerhaften Anmeldungen" -#: ../lib/modules/sambaDomain.inc:225 +#: ../lib/modules/sambaDomain.inc:224 msgid "Lockout users after bad logon attempts must be between 0 and 999." msgstr "" "Die Anzahl fehlerhafter Anmeldungen bis zur Sperrung muss zwischen 0 und 999 " @@ -8102,7 +8105,7 @@ msgstr "Ziel für Logging" msgid "Log level" msgstr "Log-Level" -#: ../templates/tools/multiEdit.php:473 +#: ../templates/tools/multiEdit.php:475 msgid "Log output" msgstr "Logausgaben" @@ -8112,41 +8115,41 @@ msgstr "Logging" #: ../templates/login.php:404 ../templates/config/conflogin.php:71 #: ../templates/config/mainlogin.php:68 -#: ../templates/selfService/selfServiceLogin.php:223 -#: ../templates/selfService/selfServiceLogin.php:326 +#: ../templates/selfService/selfServiceLogin.php:224 +#: ../templates/selfService/selfServiceLogin.php:327 #: ../templates/selfService/adminLogin.php:57 #: ../templates/selfService/selfService2Factor.php:132 #: ../templates/3rdParty/pla/lib/HTMLTree.php:497 -#: ../templates/login2Factor.php:113 ../help/help.inc:60 +#: ../templates/login2Factor.php:113 ../lib/2factor.inc:545 ../help/help.inc:60 msgid "Login" msgstr "Login" -#: ../templates/selfService/adminMain.php:551 ../help/help.inc:278 +#: ../templates/selfService/adminMain.php:563 ../help/help.inc:280 msgid "Login attribute label" msgstr "Beschriftung des Login-Attributs" -#: ../templates/selfService/adminMain.php:558 ../help/help.inc:280 +#: ../templates/selfService/adminMain.php:570 ../help/help.inc:282 msgid "Login caption" msgstr "Login-Überschrift" -#: ../templates/selfService/adminMain.php:563 ../help/help.inc:322 +#: ../templates/selfService/adminMain.php:575 ../help/help.inc:326 msgid "Login footer" msgstr "Login-Fußzeile" -#: ../templates/config/confmain.php:423 ../help/help.inc:126 +#: ../templates/config/confmain.php:427 ../help/help.inc:126 msgid "Login method" msgstr "Login-Methode" #: ../lib/types/user.inc:106 ../lib/modules/posixAccount.inc:176 -#: ../lib/modules/posixAccount.inc:246 ../lib/modules/posixAccount.inc:301 -#: ../lib/modules/posixAccount.inc:430 ../lib/modules/posixAccount.inc:1697 -#: ../lib/modules/posixAccount.inc:2031 ../lib/modules/posixAccount.inc:2151 -#: ../lib/modules/posixAccount.inc:3240 +#: ../lib/modules/posixAccount.inc:251 ../lib/modules/posixAccount.inc:306 +#: ../lib/modules/posixAccount.inc:435 ../lib/modules/posixAccount.inc:1678 +#: ../lib/modules/posixAccount.inc:1997 ../lib/modules/posixAccount.inc:2109 +#: ../lib/modules/posixAccount.inc:3199 msgid "Login shell" msgstr "Login Shell" -#: ../lib/modules/posixAccount.inc:186 ../lib/modules/posixAccount.inc:376 -#: ../lib/modules/posixAccount.inc:2334 +#: ../lib/modules/posixAccount.inc:186 ../lib/modules/posixAccount.inc:381 +#: ../lib/modules/posixAccount.inc:2292 msgid "Login shells" msgstr "Login-Shells" @@ -8163,8 +8166,8 @@ msgstr "Logodatei gelöscht." msgid "Logo is still in use by PDF structure \"%s\" in account type \"%s\"." msgstr "Logo wird noch verwendet von PDF-Struktur \"%s\" im Kontotyp \"%s\"." -#: ../lib/modules/sambaDomain.inc:109 ../lib/modules/sambaDomain.inc:192 -#: ../lib/modules/sambaDomain.inc:290 ../lib/modules/sambaDomain.inc:610 +#: ../lib/modules/sambaDomain.inc:108 ../lib/modules/sambaDomain.inc:191 +#: ../lib/modules/sambaDomain.inc:283 ../lib/modules/sambaDomain.inc:607 msgid "Logon for password change" msgstr "Anmeldung für Passwortänderung" @@ -8172,9 +8175,9 @@ msgstr "Anmeldung für Passwortänderung" #: ../lib/modules/sambaSamAccount.inc:341 #: ../lib/modules/sambaSamAccount.inc:344 #: ../lib/modules/sambaSamAccount.inc:534 -#: ../lib/modules/sambaSamAccount.inc:1229 -#: ../lib/modules/sambaSamAccount.inc:1756 -#: ../lib/modules/sambaSamAccount.inc:1909 +#: ../lib/modules/sambaSamAccount.inc:1276 +#: ../lib/modules/sambaSamAccount.inc:1803 +#: ../lib/modules/sambaSamAccount.inc:1956 msgid "Logon hours" msgstr "Anmeldezeiten" @@ -8183,13 +8186,13 @@ msgstr "Anmeldezeiten" #: ../lib/modules/sambaSamAccount.inc:225 #: ../lib/modules/sambaSamAccount.inc:313 #: ../lib/modules/sambaSamAccount.inc:518 -#: ../lib/modules/sambaSamAccount.inc:1182 -#: ../lib/modules/sambaSamAccount.inc:1732 -#: ../lib/modules/sambaSamAccount.inc:1906 -#: ../lib/modules/sambaSamAccount.inc:1925 ../lib/modules/windowsUser.inc:230 +#: ../lib/modules/sambaSamAccount.inc:1224 +#: ../lib/modules/sambaSamAccount.inc:1779 +#: ../lib/modules/sambaSamAccount.inc:1953 +#: ../lib/modules/sambaSamAccount.inc:1972 ../lib/modules/windowsUser.inc:230 #: ../lib/modules/windowsUser.inc:576 ../lib/modules/windowsUser.inc:765 #: ../lib/modules/windowsUser.inc:862 ../lib/modules/windowsUser.inc:1024 -#: ../lib/modules/windowsUser.inc:1251 ../lib/modules/windowsUser.inc:2773 +#: ../lib/modules/windowsUser.inc:1258 ../lib/modules/windowsUser.inc:2745 msgid "Logon script" msgstr "Anmeldeskript" @@ -8200,7 +8203,7 @@ msgid "Logon script is invalid!" msgstr "Anmeldeskript ist ungültig!" #: ../templates/selfService/selfServiceMain.php:71 -#: ../templates/selfService/selfServiceMain.php:456 ../lib/adminHeader.inc:132 +#: ../templates/selfService/selfServiceMain.php:457 ../lib/adminHeader.inc:132 msgid "Logout" msgstr "Abmelden" @@ -8239,14 +8242,14 @@ msgstr "MX-Eintrag" msgid "MX records" msgstr "MX-Einträge" -#: ../lib/modules/posixGroup.inc:539 ../lib/modules/posixGroup.inc:602 -#: ../lib/modules/posixGroup.inc:631 ../lib/modules/posixGroup.inc:768 -#: ../lib/modules/posixAccount.inc:393 ../lib/modules/posixAccount.inc:2186 -#: ../lib/modules/posixAccount.inc:2243 ../lib/modules/posixAccount.inc:2314 +#: ../lib/modules/posixGroup.inc:488 ../lib/modules/posixGroup.inc:551 +#: ../lib/modules/posixGroup.inc:580 ../lib/modules/posixGroup.inc:717 +#: ../lib/modules/posixAccount.inc:398 ../lib/modules/posixAccount.inc:2144 +#: ../lib/modules/posixAccount.inc:2201 ../lib/modules/posixAccount.inc:2272 msgid "Magic number" msgstr "Magische Zahl" -#: ../lib/modules/posixGroup.inc:511 ../lib/modules/posixAccount.inc:382 +#: ../lib/modules/posixGroup.inc:460 ../lib/modules/posixAccount.inc:387 msgid "" "Magic number will set a fixed value that must match your server " "configuration." @@ -8269,9 +8272,9 @@ msgstr "Mail-Routing" #: ../lib/modules/selfRegistration.inc:741 #: ../lib/modules/selfRegistration.inc:761 -#: ../lib/modules/passwordSelfReset.inc:1820 -#: ../lib/modules/passwordSelfReset.inc:1835 -#: ../lib/modules/passwordSelfReset.inc:1868 +#: ../lib/modules/passwordSelfReset.inc:1818 +#: ../lib/modules/passwordSelfReset.inc:1833 +#: ../lib/modules/passwordSelfReset.inc:1866 msgid "Mail sending failed." msgstr "Der email-Versand ist fehlgeschlagen." @@ -8299,7 +8302,7 @@ msgstr "Mailserver (\"MX\"-Einträge)" msgid "Mail source" msgstr "Mailquelle" -#: ../lib/account.inc:1270 +#: ../lib/account.inc:1285 #, php-format msgid "Mail successfully sent to %s." msgstr "Mail wurde erfolgreich an %s gesendet." @@ -8308,8 +8311,8 @@ msgstr "Mail wurde erfolgreich an %s gesendet." #: ../lib/modules/asteriskAccount.inc:276 #: ../lib/modules/asteriskAccount.inc:397 #: ../lib/modules/asteriskAccount.inc:655 -#: ../lib/modules/asteriskAccount.inc:993 -#: ../lib/modules/asteriskAccount.inc:1237 +#: ../lib/modules/asteriskAccount.inc:994 +#: ../lib/modules/asteriskAccount.inc:1238 #: ../lib/modules/courierMailAccount.inc:112 #: ../lib/modules/courierMailAccount.inc:147 #: ../lib/modules/courierMailAccount.inc:159 @@ -8366,11 +8369,11 @@ msgstr "Mails an diesen Namen werden an die Empfänger weitergeleitet." msgid "Main" msgstr "Allgemein" -#: ../templates/selfService/adminMain.php:568 ../help/help.inc:282 +#: ../templates/selfService/adminMain.php:580 ../help/help.inc:284 msgid "Main page caption" msgstr "Überschrift der Hauptseite" -#: ../templates/selfService/adminMain.php:573 ../help/help.inc:324 +#: ../templates/selfService/adminMain.php:585 ../help/help.inc:328 msgid "Main page footer" msgstr "Fußzeile der Hauptseite" @@ -8395,7 +8398,7 @@ msgstr "Profile verwalten" msgid "Manage logos" msgstr "Logos verwalten" -#: ../lib/modules/sambaSamAccount.inc:1169 +#: ../lib/modules/sambaSamAccount.inc:1211 msgid "Manage profile directory" msgstr "Profilverzeichnis verwalten" @@ -8407,12 +8410,12 @@ msgstr "SelfService-Profile verwalten" msgid "Manage server profiles" msgstr "Serverprofile verwalten" -#: ../lib/modules/windowsHost.inc:86 ../lib/modules/windowsHost.inc:112 -#: ../lib/modules/windowsHost.inc:122 ../lib/modules/windowsHost.inc:146 -#: ../lib/modules/windowsHost.inc:291 ../lib/modules/windowsGroup.inc:155 +#: ../lib/modules/windowsHost.inc:85 ../lib/modules/windowsHost.inc:111 +#: ../lib/modules/windowsHost.inc:121 ../lib/modules/windowsHost.inc:145 +#: ../lib/modules/windowsHost.inc:294 ../lib/modules/windowsGroup.inc:155 #: ../lib/modules/windowsGroup.inc:222 ../lib/modules/windowsGroup.inc:264 -#: ../lib/modules/windowsGroup.inc:348 ../lib/modules/windowsGroup.inc:931 -#: ../lib/modules/windowsGroup.inc:1101 +#: ../lib/modules/windowsGroup.inc:344 ../lib/modules/windowsGroup.inc:913 +#: ../lib/modules/windowsGroup.inc:1082 msgid "Managed by" msgstr "Verwaltet von" @@ -8424,12 +8427,12 @@ msgstr "Verwaltete Suffixe" #: ../lib/modules/ipHost.inc:103 ../lib/modules/ipHost.inc:114 #: ../lib/modules/ipHost.inc:164 ../lib/modules/ipHost.inc:300 #: ../lib/modules/windowsUser.inc:354 ../lib/modules/windowsUser.inc:754 -#: ../lib/modules/windowsUser.inc:919 ../lib/modules/windowsUser.inc:1199 -#: ../lib/modules/windowsUser.inc:2750 ../lib/modules/windowsUser.inc:3651 +#: ../lib/modules/windowsUser.inc:919 ../lib/modules/windowsUser.inc:1198 +#: ../lib/modules/windowsUser.inc:2722 ../lib/modules/windowsUser.inc:3623 #: ../lib/modules/inetOrgPerson.inc:291 ../lib/modules/inetOrgPerson.inc:546 #: ../lib/modules/inetOrgPerson.inc:590 ../lib/modules/inetOrgPerson.inc:594 -#: ../lib/modules/inetOrgPerson.inc:1535 ../lib/modules/inetOrgPerson.inc:1949 -#: ../lib/modules/inetOrgPerson.inc:3879 ../lib/modules/inetOrgPerson.inc:3914 +#: ../lib/modules/inetOrgPerson.inc:1535 ../lib/modules/inetOrgPerson.inc:1950 +#: ../lib/modules/inetOrgPerson.inc:3880 ../lib/modules/inetOrgPerson.inc:3915 msgid "Manager" msgstr "Vorgesetzter" @@ -8445,7 +8448,7 @@ msgstr "Manuell" msgid "Manual if conflicts" msgstr "Manuell bei Konflikten" -#: ../lib/modules/customScripts.inc:159 +#: ../lib/modules/customScripts.inc:157 msgid "Manual scripts" msgstr "Manuelle Skripte" @@ -8510,22 +8513,22 @@ msgstr "Max. Anzahl Dateideskriptoren" msgid "Maximum" msgstr "Maximum" -#: ../lib/modules/posixGroup.inc:622 ../lib/modules/posixGroup.inc:757 -#: ../lib/modules/posixGroup.inc:758 +#: ../lib/modules/posixGroup.inc:571 ../lib/modules/posixGroup.inc:706 +#: ../lib/modules/posixGroup.inc:707 msgid "Maximum GID number" msgstr "Maximale GID-Nummer" -#: ../lib/modules/posixGroup.inc:757 +#: ../lib/modules/posixGroup.inc:706 msgid "Maximum GID number is invalid or empty!" msgstr "Maximale GID-Nummer ist ungültig oder leer!" -#: ../lib/modules/posixGroup.inc:758 +#: ../lib/modules/posixGroup.inc:707 msgid "Maximum GID number must be greater than minimum GID number!" msgstr "Die maximale GID-Nummer muss größer sein als die minimale GID-Nummer!" #: ../lib/modules/posixAccount.inc:84 ../lib/modules/posixAccount.inc:86 #: ../lib/modules/posixAccount.inc:87 ../lib/modules/posixAccount.inc:88 -#: ../lib/modules/posixAccount.inc:2240 ../lib/modules/posixAccount.inc:2311 +#: ../lib/modules/posixAccount.inc:2198 ../lib/modules/posixAccount.inc:2269 msgid "Maximum UID number" msgstr "Maximale UID-Nummer" @@ -8537,9 +8540,9 @@ msgstr "Maximale UID-Nummer ist ungültig!" msgid "Maximum UID number must be greater than minimum UID number!" msgstr "Die maximale UID-Nummer muss größer sein als die minimale UID-Nummer!" -#: ../lib/types/ppolicyType.inc:87 ../lib/modules/ppolicy.inc:108 -#: ../lib/modules/ppolicy.inc:152 ../lib/modules/ppolicy.inc:172 -#: ../lib/modules/ppolicy.inc:268 ../lib/modules/ppolicy.inc:476 +#: ../lib/types/ppolicyType.inc:87 ../lib/modules/ppolicy.inc:107 +#: ../lib/modules/ppolicy.inc:151 ../lib/modules/ppolicy.inc:171 +#: ../lib/modules/ppolicy.inc:261 ../lib/modules/ppolicy.inc:469 msgid "Maximum failure count" msgstr "Maximale Fehlerzahl" @@ -8549,8 +8552,8 @@ msgstr "Maximale Fehlerzahl" msgid "Maximum file size" msgstr "Maximale Dateigröße" -#: ../lib/modules/windowsUser.inc:3675 ../lib/modules/inetOrgPerson.inc:2495 -#: ../lib/modules/inetOrgPerson.inc:2519 ../lib/modules/inetOrgPerson.inc:3931 +#: ../lib/modules/windowsUser.inc:3647 ../lib/modules/inetOrgPerson.inc:2496 +#: ../lib/modules/inetOrgPerson.inc:2520 ../lib/modules/inetOrgPerson.inc:3931 msgid "Maximum file size (kB)" msgstr "Maximale Dateigröße (kB)" @@ -8560,15 +8563,15 @@ msgstr "" "Die maximale Dateigröße muss eine Zahl sein. Bitte geben Sie 0 für " "unbegrenzt ein." -#: ../lib/modules/windowsUser.inc:3674 ../lib/modules/inetOrgPerson.inc:2494 -#: ../lib/modules/inetOrgPerson.inc:2516 ../lib/modules/inetOrgPerson.inc:3930 +#: ../lib/modules/windowsUser.inc:3646 ../lib/modules/inetOrgPerson.inc:2495 +#: ../lib/modules/inetOrgPerson.inc:2517 ../lib/modules/inetOrgPerson.inc:3930 msgid "Maximum height (px)" msgstr "Maximale Höhe (px)" #: ../lib/types/pykotaPrinterType.inc:97 ../lib/modules/pykotaPrinter.inc:102 #: ../lib/modules/pykotaPrinter.inc:134 ../lib/modules/pykotaPrinter.inc:158 #: ../lib/modules/pykotaPrinter.inc:194 ../lib/modules/pykotaPrinter.inc:212 -#: ../lib/modules/pykotaPrinter.inc:232 ../lib/modules/pykotaPrinter.inc:581 +#: ../lib/modules/pykotaPrinter.inc:232 ../lib/modules/pykotaPrinter.inc:573 msgid "Maximum job size" msgstr "Maximale Jobgröße" @@ -8582,7 +8585,7 @@ msgstr "Maximale Lease-Zeit" msgid "Maximum length" msgstr "Maximale Länge" -#: ../lib/lists.inc:1194 ../help/help.inc:101 +#: ../lib/lists.inc:1193 ../help/help.inc:101 msgid "Maximum list entries" msgstr "Maximale Listeneinträge" @@ -8596,19 +8599,19 @@ msgstr "" #: ../lib/types/ppolicyType.inc:82 ../lib/modules/shadowAccount.inc:58 #: ../lib/modules/shadowAccount.inc:64 ../lib/modules/shadowAccount.inc:125 #: ../lib/modules/shadowAccount.inc:173 ../lib/modules/shadowAccount.inc:190 -#: ../lib/modules/shadowAccount.inc:228 ../lib/modules/shadowAccount.inc:344 -#: ../lib/modules/shadowAccount.inc:575 ../lib/modules/ppolicy.inc:88 -#: ../lib/modules/ppolicy.inc:144 ../lib/modules/ppolicy.inc:167 -#: ../lib/modules/ppolicy.inc:168 ../lib/modules/ppolicy.inc:205 -#: ../lib/modules/ppolicy.inc:472 ../lib/modules/sambaDomain.inc:129 -#: ../lib/modules/sambaDomain.inc:197 ../lib/modules/sambaDomain.inc:220 -#: ../lib/modules/sambaDomain.inc:222 ../lib/modules/sambaDomain.inc:332 -#: ../lib/modules/sambaDomain.inc:603 +#: ../lib/modules/shadowAccount.inc:228 ../lib/modules/shadowAccount.inc:349 +#: ../lib/modules/shadowAccount.inc:602 ../lib/modules/ppolicy.inc:87 +#: ../lib/modules/ppolicy.inc:143 ../lib/modules/ppolicy.inc:166 +#: ../lib/modules/ppolicy.inc:167 ../lib/modules/ppolicy.inc:198 +#: ../lib/modules/ppolicy.inc:465 ../lib/modules/sambaDomain.inc:128 +#: ../lib/modules/sambaDomain.inc:196 ../lib/modules/sambaDomain.inc:219 +#: ../lib/modules/sambaDomain.inc:221 ../lib/modules/sambaDomain.inc:325 +#: ../lib/modules/sambaDomain.inc:600 msgid "Maximum password age" msgstr "Maximales Passwortalter" -#: ../lib/modules/windowsUser.inc:3673 ../lib/modules/inetOrgPerson.inc:2493 -#: ../lib/modules/inetOrgPerson.inc:2513 ../lib/modules/inetOrgPerson.inc:3929 +#: ../lib/modules/windowsUser.inc:3645 ../lib/modules/inetOrgPerson.inc:2494 +#: ../lib/modules/inetOrgPerson.inc:2514 ../lib/modules/inetOrgPerson.inc:3929 msgid "Maximum width (px)" msgstr "Maximale Breite (px)" @@ -8620,34 +8623,34 @@ msgstr "Anzahl Mitglieder" #: ../lib/modules/qmailGroup.inc:169 ../lib/modules/qmailGroup.inc:173 #: ../lib/modules/qmailGroup.inc:302 ../lib/modules/qmailGroup.inc:367 -#: ../lib/modules/qmailGroup.inc:390 ../lib/modules/qmailGroup.inc:479 -#: ../lib/modules/qmailGroup.inc:980 ../lib/modules/qmailGroup.inc:1017 +#: ../lib/modules/qmailGroup.inc:390 ../lib/modules/qmailGroup.inc:421 +#: ../lib/modules/qmailGroup.inc:831 ../lib/modules/qmailGroup.inc:868 msgid "Member email addresses" msgstr "Email-Adressen der Mitglieder" #: ../lib/modules/qmailGroup.inc:121 ../lib/modules/qmailGroup.inc:125 #: ../lib/modules/qmailGroup.inc:244 ../lib/modules/qmailGroup.inc:346 -#: ../lib/modules/qmailGroup.inc:481 ../lib/modules/qmailGroup.inc:749 -#: ../lib/modules/qmailGroup.inc:979 ../lib/modules/qmailGroup.inc:1016 +#: ../lib/modules/qmailGroup.inc:423 ../lib/modules/qmailGroup.inc:601 +#: ../lib/modules/qmailGroup.inc:830 ../lib/modules/qmailGroup.inc:867 msgid "Member entries" msgstr "Mitglieder" #: ../lib/modules/qmailGroup.inc:145 ../lib/modules/qmailGroup.inc:149 #: ../lib/modules/qmailGroup.inc:268 ../lib/modules/qmailGroup.inc:355 -#: ../lib/modules/qmailGroup.inc:480 ../lib/modules/qmailGroup.inc:981 -#: ../lib/modules/qmailGroup.inc:1018 +#: ../lib/modules/qmailGroup.inc:422 ../lib/modules/qmailGroup.inc:832 +#: ../lib/modules/qmailGroup.inc:869 msgid "Member filter" msgstr "Filter für Mitglieder" #: ../lib/modules/windowsGroup.inc:139 ../lib/modules/windowsGroup.inc:256 -#: ../lib/modules/windowsGroup.inc:405 ../lib/modules/windowsGroup.inc:955 +#: ../lib/modules/windowsGroup.inc:405 ../lib/modules/windowsGroup.inc:937 msgid "Member of" msgstr "Mitglied von" -#: ../lib/types/netgroup.inc:97 ../lib/modules/nisnetgroup.inc:98 -#: ../lib/modules/nisnetgroup.inc:102 ../lib/modules/nisnetgroup.inc:135 -#: ../lib/modules/nisnetgroup.inc:145 ../lib/modules/nisnetgroup.inc:299 -#: ../lib/modules/nisnetgroup.inc:590 ../lib/modules/organizationalRole.inc:97 +#: ../lib/types/netgroup.inc:97 ../lib/modules/nisnetgroup.inc:97 +#: ../lib/modules/nisnetgroup.inc:101 ../lib/modules/nisnetgroup.inc:134 +#: ../lib/modules/nisnetgroup.inc:144 ../lib/modules/nisnetgroup.inc:223 +#: ../lib/modules/nisnetgroup.inc:574 ../lib/modules/organizationalRole.inc:97 #: ../lib/modules/organizationalRole.inc:101 #: ../lib/modules/organizationalRole.inc:134 #: ../lib/modules/organizationalRole.inc:142 @@ -8656,7 +8659,7 @@ msgstr "Mitglied von" #: ../lib/modules/organizationalRole.inc:489 #: ../lib/modules/windowsGroup.inc:135 ../lib/modules/windowsGroup.inc:143 #: ../lib/modules/windowsGroup.inc:206 ../lib/modules/windowsGroup.inc:255 -#: ../lib/modules/windowsGroup.inc:940 ../lib/modules/groupOfNames.inc:104 +#: ../lib/modules/windowsGroup.inc:922 ../lib/modules/groupOfNames.inc:104 #: ../lib/modules/groupOfNames.inc:108 ../lib/modules/groupOfNames.inc:154 #: ../lib/modules/groupOfNames.inc:162 ../lib/modules/groupOfNames.inc:171 #: ../lib/modules/groupOfNames.inc:225 ../lib/modules/groupOfNames.inc:512 @@ -8669,8 +8672,8 @@ msgid "Members are optional" msgstr "Mitglieder sind optional" #: ../lib/modules/qmailGroup.inc:161 ../lib/modules/qmailGroup.inc:284 -#: ../lib/modules/qmailGroup.inc:361 ../lib/modules/qmailGroup.inc:582 -#: ../lib/modules/qmailGroup.inc:992 ../lib/modules/qmailGroup.inc:1024 +#: ../lib/modules/qmailGroup.inc:361 ../lib/modules/qmailGroup.inc:481 +#: ../lib/modules/qmailGroup.inc:843 ../lib/modules/qmailGroup.inc:875 msgid "Members only" msgstr "Nur Mitglieder" @@ -8680,24 +8683,24 @@ msgstr "Mitgliedschaften" #: ../lib/modules/qmailUser.inc:110 ../lib/modules/qmailUser.inc:189 #: ../lib/modules/qmailUser.inc:273 ../lib/modules/qmailUser.inc:366 -#: ../lib/modules/qmailUser.inc:651 ../lib/modules/qmailUser.inc:1096 -#: ../lib/modules/qmailUser.inc:1216 +#: ../lib/modules/qmailUser.inc:531 ../lib/modules/qmailUser.inc:936 +#: ../lib/modules/qmailUser.inc:1056 msgid "Message count limit" msgstr "Max. Anzahl Nachrichten" #: ../lib/modules/qmailUser.inc:120 ../lib/modules/qmailUser.inc:193 #: ../lib/modules/qmailUser.inc:281 ../lib/modules/qmailUser.inc:369 -#: ../lib/modules/qmailUser.inc:662 ../lib/modules/qmailUser.inc:1097 -#: ../lib/modules/qmailUser.inc:1217 +#: ../lib/modules/qmailUser.inc:536 ../lib/modules/qmailUser.inc:937 +#: ../lib/modules/qmailUser.inc:1057 msgid "Message size limit" msgstr "Größenlimit für Nachrichten" #: ../lib/modules/qmailUser.inc:138 ../lib/modules/qmailUser.inc:213 #: ../lib/modules/qmailUser.inc:323 ../lib/modules/qmailUser.inc:384 -#: ../lib/modules/qmailUser.inc:566 ../lib/modules/qmailUser.inc:1100 -#: ../lib/modules/qmailUser.inc:1222 ../lib/modules/qmailGroup.inc:104 +#: ../lib/modules/qmailUser.inc:470 ../lib/modules/qmailUser.inc:940 +#: ../lib/modules/qmailUser.inc:1062 ../lib/modules/qmailGroup.inc:104 #: ../lib/modules/qmailGroup.inc:219 ../lib/modules/qmailGroup.inc:337 -#: ../lib/modules/qmailGroup.inc:473 ../lib/modules/qmailGroup.inc:975 +#: ../lib/modules/qmailGroup.inc:418 ../lib/modules/qmailGroup.inc:826 msgid "Message store" msgstr "Nachrichtenspeicher" @@ -8709,8 +8712,8 @@ msgstr "Nachrichten" msgid "Miller" msgstr "Müller" -#: ../lib/modules/sambaDomain.inc:101 ../lib/modules/sambaDomain.inc:190 -#: ../lib/modules/sambaDomain.inc:276 ../lib/modules/sambaDomain.inc:599 +#: ../lib/modules/sambaDomain.inc:100 ../lib/modules/sambaDomain.inc:189 +#: ../lib/modules/sambaDomain.inc:269 ../lib/modules/sambaDomain.inc:596 msgid "Minimal password length" msgstr "Minimale Passwortlänge" @@ -8719,16 +8722,16 @@ msgstr "Minimale Passwortlänge" msgid "Minimum" msgstr "Minimum" -#: ../lib/modules/posixGroup.inc:619 ../lib/modules/posixGroup.inc:756 +#: ../lib/modules/posixGroup.inc:568 ../lib/modules/posixGroup.inc:705 msgid "Minimum GID number" msgstr "Minimale GID-Nummer" -#: ../lib/modules/posixGroup.inc:756 +#: ../lib/modules/posixGroup.inc:705 msgid "Minimum GID number is invalid or empty!" msgstr "Minimale GID-Nummer ist ungültig oder leer!" #: ../lib/modules/posixAccount.inc:83 ../lib/modules/posixAccount.inc:85 -#: ../lib/modules/posixAccount.inc:2237 ../lib/modules/posixAccount.inc:2308 +#: ../lib/modules/posixAccount.inc:2195 ../lib/modules/posixAccount.inc:2266 msgid "Minimum UID number" msgstr "Minimale UID-Nummer" @@ -8737,7 +8740,7 @@ msgid "Minimum UID number is invalid!" msgstr "Minimale UID-Nummer ist ungültig!" #: ../lib/modules/passwordSelfReset.inc:95 -#: ../lib/modules/passwordSelfReset.inc:643 +#: ../lib/modules/passwordSelfReset.inc:641 msgid "Minimum answer length" msgstr "Minimale Antwortlänge" @@ -8756,19 +8759,19 @@ msgstr "Minimale Anzahl von Zahlen" #: ../lib/types/ppolicyType.inc:81 ../lib/modules/shadowAccount.inc:56 #: ../lib/modules/shadowAccount.inc:121 ../lib/modules/shadowAccount.inc:172 #: ../lib/modules/shadowAccount.inc:186 ../lib/modules/shadowAccount.inc:222 -#: ../lib/modules/shadowAccount.inc:336 ../lib/modules/shadowAccount.inc:574 -#: ../lib/modules/ppolicy.inc:84 ../lib/modules/ppolicy.inc:143 -#: ../lib/modules/ppolicy.inc:166 ../lib/modules/ppolicy.inc:198 -#: ../lib/modules/ppolicy.inc:471 ../lib/modules/sambaDomain.inc:125 -#: ../lib/modules/sambaDomain.inc:196 ../lib/modules/sambaDomain.inc:221 -#: ../lib/modules/sambaDomain.inc:324 ../lib/modules/sambaDomain.inc:602 +#: ../lib/modules/shadowAccount.inc:346 ../lib/modules/shadowAccount.inc:601 +#: ../lib/modules/ppolicy.inc:83 ../lib/modules/ppolicy.inc:142 +#: ../lib/modules/ppolicy.inc:165 ../lib/modules/ppolicy.inc:191 +#: ../lib/modules/ppolicy.inc:464 ../lib/modules/sambaDomain.inc:124 +#: ../lib/modules/sambaDomain.inc:195 ../lib/modules/sambaDomain.inc:220 +#: ../lib/modules/sambaDomain.inc:317 ../lib/modules/sambaDomain.inc:599 msgid "Minimum password age" msgstr "Minimales Passwortalter" #: ../templates/config/mainmanage.php:392 ../lib/types/ppolicyType.inc:90 -#: ../lib/modules/ppolicy.inc:120 ../lib/modules/ppolicy.inc:147 -#: ../lib/modules/ppolicy.inc:175 ../lib/modules/ppolicy.inc:247 -#: ../lib/modules/ppolicy.inc:479 +#: ../lib/modules/ppolicy.inc:119 ../lib/modules/ppolicy.inc:146 +#: ../lib/modules/ppolicy.inc:174 ../lib/modules/ppolicy.inc:240 +#: ../lib/modules/ppolicy.inc:472 msgid "Minimum password length" msgstr "Minimale Passwortlänge" @@ -8796,42 +8799,42 @@ msgstr "Fehlende Änderungsanweisung add, delete oder replace" #: ../lib/modules/windowsUser.inc:374 ../lib/modules/windowsUser.inc:633 #: ../lib/modules/windowsUser.inc:931 ../lib/modules/windowsUser.inc:1008 -#: ../lib/modules/windowsUser.inc:1149 ../lib/modules/windowsUser.inc:2734 -#: ../lib/modules/windowsUser.inc:3631 +#: ../lib/modules/windowsUser.inc:1148 ../lib/modules/windowsUser.inc:2706 +#: ../lib/modules/windowsUser.inc:3603 msgid "Mobile" msgstr "Mobil" -#: ../lib/types/user.inc:223 ../lib/modules/inetOrgPerson.inc:76 +#: ../lib/types/user.inc:224 ../lib/modules/inetOrgPerson.inc:76 #: ../lib/modules/inetOrgPerson.inc:159 ../lib/modules/inetOrgPerson.inc:363 #: ../lib/modules/inetOrgPerson.inc:516 ../lib/modules/inetOrgPerson.inc:638 #: ../lib/modules/inetOrgPerson.inc:642 ../lib/modules/inetOrgPerson.inc:1403 -#: ../lib/modules/inetOrgPerson.inc:1406 ../lib/modules/inetOrgPerson.inc:1959 -#: ../lib/modules/inetOrgPerson.inc:3870 ../lib/modules/inetOrgPerson.inc:3909 +#: ../lib/modules/inetOrgPerson.inc:1406 ../lib/modules/inetOrgPerson.inc:1960 +#: ../lib/modules/inetOrgPerson.inc:3871 ../lib/modules/inetOrgPerson.inc:3910 msgid "Mobile number" msgstr "Mobil" -#: ../lib/modules/inetOrgPerson.inc:2614 +#: ../lib/modules/inetOrgPerson.inc:2615 msgid "Mobile telephone number" msgstr "Handynummer" #: ../lib/modules/qmailGroup.inc:177 ../lib/modules/qmailGroup.inc:181 #: ../lib/modules/qmailGroup.inc:310 ../lib/modules/qmailGroup.inc:370 -#: ../lib/modules/qmailGroup.inc:392 ../lib/modules/qmailGroup.inc:482 -#: ../lib/modules/qmailGroup.inc:983 ../lib/modules/qmailGroup.inc:1020 +#: ../lib/modules/qmailGroup.inc:392 ../lib/modules/qmailGroup.inc:424 +#: ../lib/modules/qmailGroup.inc:834 ../lib/modules/qmailGroup.inc:871 msgid "Moderator email addresses" msgstr "Email-Adressen der Moderatoren" #: ../lib/modules/qmailGroup.inc:129 ../lib/modules/qmailGroup.inc:133 #: ../lib/modules/qmailGroup.inc:252 ../lib/modules/qmailGroup.inc:349 -#: ../lib/modules/qmailGroup.inc:483 ../lib/modules/qmailGroup.inc:752 -#: ../lib/modules/qmailGroup.inc:982 ../lib/modules/qmailGroup.inc:1019 +#: ../lib/modules/qmailGroup.inc:425 ../lib/modules/qmailGroup.inc:604 +#: ../lib/modules/qmailGroup.inc:833 ../lib/modules/qmailGroup.inc:870 msgid "Moderator entries" msgstr "Moderatoren" #: ../lib/modules/qmailGroup.inc:72 ../lib/modules/qmailGroup.inc:165 #: ../lib/modules/qmailGroup.inc:294 ../lib/modules/qmailGroup.inc:364 -#: ../lib/modules/qmailGroup.inc:568 ../lib/modules/qmailGroup.inc:978 -#: ../lib/modules/qmailGroup.inc:1015 +#: ../lib/modules/qmailGroup.inc:471 ../lib/modules/qmailGroup.inc:829 +#: ../lib/modules/qmailGroup.inc:866 msgid "Moderator text" msgstr "Moderationstext" @@ -8861,7 +8864,7 @@ msgid "Modified by" msgstr "Geändert von" #: ../templates/tools/serverInfo.php:286 ../templates/tools/serverInfo.php:353 -#: ../templates/tools/multiEdit.php:148 +#: ../templates/tools/multiEdit.php:150 msgid "Modify" msgstr "Ändern" @@ -8883,7 +8886,7 @@ msgstr "Gruppenmitglieder ändern" msgid "Modifying" msgstr "Ändere" -#: ../templates/selfService/adminMain.php:403 ../lib/configPages.inc:102 +#: ../templates/selfService/adminMain.php:409 ../lib/configPages.inc:102 msgid "Module settings" msgstr "Moduleinstellungen" @@ -8891,12 +8894,12 @@ msgstr "Moduleinstellungen" msgid "Modules" msgstr "Module" -#: ../lib/modules/sambaSamAccount.inc:1336 +#: ../lib/modules/sambaSamAccount.inc:1386 msgid "Monday" msgstr "Montag" -#: ../lib/modules/quota.inc:109 ../lib/modules/quota.inc:485 -#: ../lib/modules/quota.inc:742 ../lib/modules/systemQuotas.inc:99 +#: ../lib/modules/quota.inc:109 ../lib/modules/quota.inc:467 +#: ../lib/modules/quota.inc:711 ../lib/modules/systemQuotas.inc:99 #: ../lib/modules/systemQuotas.inc:124 ../lib/modules/systemQuotas.inc:370 msgid "Mountpoint" msgstr "Mountpunkt" @@ -8921,7 +8924,7 @@ msgstr "Nach unten" msgid "Move up" msgstr "Nach oben" -#: ../templates/tools/multiEdit.php:102 ../lib/tools/multiEdit.inc:45 +#: ../templates/tools/multiEdit.php:104 ../lib/tools/multiEdit.inc:45 msgid "Multi edit" msgstr "Multieditor" @@ -8992,8 +8995,8 @@ msgstr "Mehrere Werte werden per Strichpunkt getrennt." #: ../lib/modules/asteriskAccount.inc:288 #: ../lib/modules/asteriskAccount.inc:484 #: ../lib/modules/asteriskAccount.inc:715 -#: ../lib/modules/asteriskAccount.inc:1005 -#: ../lib/modules/asteriskAccount.inc:1249 +#: ../lib/modules/asteriskAccount.inc:1006 +#: ../lib/modules/asteriskAccount.inc:1250 msgid "Music on hold" msgstr "Wartemusik" @@ -9001,7 +9004,7 @@ msgstr "Wartemusik" msgid "Music to play on hold." msgstr "Wartemusik die gespielt werden soll." -#: ../lib/modules/ipHost.inc:99 ../lib/modules/windowsHost.inc:108 +#: ../lib/modules/ipHost.inc:99 ../lib/modules/windowsHost.inc:107 #: ../lib/modules/device.inc:105 ../lib/modules/windowsUser.inc:480 #: ../lib/modules/inetOrgPerson.inc:325 ../lib/modules/inetOrgPerson.inc:333 #: ../lib/modules/inetOrgPerson.inc:447 @@ -9020,8 +9023,8 @@ msgstr "Musterstraße 42" #: ../lib/modules/asteriskAccount.inc:277 #: ../lib/modules/asteriskAccount.inc:404 #: ../lib/modules/asteriskAccount.inc:667 -#: ../lib/modules/asteriskAccount.inc:994 -#: ../lib/modules/asteriskAccount.inc:1238 +#: ../lib/modules/asteriskAccount.inc:995 +#: ../lib/modules/asteriskAccount.inc:1239 msgid "NAT" msgstr "NAT" @@ -9029,17 +9032,17 @@ msgstr "NAT" msgid "NAT setting for this account." msgstr "NAT-Einstellung für diesen Account." -#: ../lib/modules/windowsUser.inc:1269 ../lib/modules/windowsGroup.inc:366 +#: ../lib/modules/windowsUser.inc:1276 ../lib/modules/windowsGroup.inc:365 msgid "NIS" msgstr "NIS" #: ../lib/modules/windowsUser.inc:274 ../lib/modules/windowsUser.inc:674 #: ../lib/modules/windowsUser.inc:769 ../lib/modules/windowsUser.inc:883 -#: ../lib/modules/windowsUser.inc:1274 ../lib/modules/windowsUser.inc:2740 -#: ../lib/modules/windowsUser.inc:3637 ../lib/modules/windowsGroup.inc:163 +#: ../lib/modules/windowsUser.inc:1281 ../lib/modules/windowsUser.inc:2712 +#: ../lib/modules/windowsUser.inc:3609 ../lib/modules/windowsGroup.inc:163 #: ../lib/modules/windowsGroup.inc:238 ../lib/modules/windowsGroup.inc:246 -#: ../lib/modules/windowsGroup.inc:270 ../lib/modules/windowsGroup.inc:371 -#: ../lib/modules/windowsGroup.inc:900 ../lib/modules/windowsGroup.inc:1103 +#: ../lib/modules/windowsGroup.inc:270 ../lib/modules/windowsGroup.inc:370 +#: ../lib/modules/windowsGroup.inc:882 ../lib/modules/windowsGroup.inc:1084 msgid "NIS domain" msgstr "NIS-Domäne" @@ -9049,11 +9052,11 @@ msgstr "NIS-Domänenname" #: ../lib/modules/windowsUser.inc:270 ../lib/modules/windowsUser.inc:666 #: ../lib/modules/windowsUser.inc:880 ../lib/modules/windowsUser.inc:1031 -#: ../lib/modules/windowsUser.inc:1271 ../lib/modules/windowsUser.inc:2739 -#: ../lib/modules/windowsUser.inc:3636 ../lib/modules/windowsGroup.inc:159 +#: ../lib/modules/windowsUser.inc:1278 ../lib/modules/windowsUser.inc:2711 +#: ../lib/modules/windowsUser.inc:3608 ../lib/modules/windowsGroup.inc:159 #: ../lib/modules/windowsGroup.inc:230 ../lib/modules/windowsGroup.inc:267 -#: ../lib/modules/windowsGroup.inc:285 ../lib/modules/windowsGroup.inc:368 -#: ../lib/modules/windowsGroup.inc:899 ../lib/modules/windowsGroup.inc:1102 +#: ../lib/modules/windowsGroup.inc:285 ../lib/modules/windowsGroup.inc:367 +#: ../lib/modules/windowsGroup.inc:881 ../lib/modules/windowsGroup.inc:1083 msgid "NIS name" msgstr "NIS-Name" @@ -9066,7 +9069,7 @@ msgstr "" "NIS-Name enthält ungültige Zeichen. Gültige Zeichen sind: a-z, A-Z, 0-9 " "und .-_!" -#: ../lib/modules/nisnetgroup.inc:70 +#: ../lib/modules/nisnetgroup.inc:69 msgid "NIS net group" msgstr "NIS Netzgruppe" @@ -9120,9 +9123,9 @@ msgstr "NS-Eintrag" #: ../lib/modules/range.inc:141 ../lib/modules/range.inc:547 #: ../lib/modules/nisObject.inc:61 ../lib/modules/nisObject.inc:81 #: ../lib/modules/nisObject.inc:111 ../lib/modules/nisObject.inc:137 -#: ../lib/modules/nisObject.inc:205 ../lib/modules/ppolicy.inc:80 -#: ../lib/modules/ppolicy.inc:142 ../lib/modules/ppolicy.inc:165 -#: ../lib/modules/ppolicy.inc:190 ../lib/modules/ppolicy.inc:470 +#: ../lib/modules/nisObject.inc:205 ../lib/modules/ppolicy.inc:79 +#: ../lib/modules/ppolicy.inc:141 ../lib/modules/ppolicy.inc:164 +#: ../lib/modules/ppolicy.inc:185 ../lib/modules/ppolicy.inc:463 #: ../lib/modules/automount.inc:63 ../lib/modules/automount.inc:79 #: ../lib/modules/automount.inc:102 ../lib/modules/automount.inc:128 #: ../lib/modules/automount.inc:179 ../lib/modules/kolabSharedFolder.inc:90 @@ -9131,12 +9134,12 @@ msgstr "NS-Eintrag" #: ../lib/modules/kolabSharedFolder.inc:222 #: ../lib/modules/kolabSharedFolder.inc:235 #: ../lib/modules/kolabSharedFolder.inc:405 -#: ../lib/modules/asteriskExtension.inc:795 ../lib/modules/nsview.inc:61 -#: ../lib/modules/nsview.inc:68 ../lib/modules/nsview.inc:84 -#: ../lib/modules/nsview.inc:107 ../lib/modules/nsview.inc:121 -#: ../lib/modules/nsview.inc:158 ../lib/modules/oracleService.inc:61 -#: ../lib/modules/oracleService.inc:77 ../lib/modules/oracleService.inc:98 -#: ../lib/modules/oracleService.inc:121 ../lib/modules/oracleService.inc:166 +#: ../lib/modules/asteriskExtension.inc:793 ../lib/modules/nsview.inc:60 +#: ../lib/modules/nsview.inc:67 ../lib/modules/nsview.inc:83 +#: ../lib/modules/nsview.inc:106 ../lib/modules/nsview.inc:120 +#: ../lib/modules/nsview.inc:157 ../lib/modules/oracleService.inc:60 +#: ../lib/modules/oracleService.inc:76 ../lib/modules/oracleService.inc:97 +#: ../lib/modules/oracleService.inc:120 ../lib/modules/oracleService.inc:164 #: ../lib/modules/customFields.inc:89 ../lib/modules/customFields.inc:1201 #: ../lib/modules/organizationalRole.inc:167 #: ../lib/modules/groupOfNames.inc:208 @@ -9180,7 +9183,7 @@ msgstr "" "Name, unter dem das Profil gespeichert wird. Falls ein Profil mit diesem " "Namen existiert wird es überschrieben." -#: ../lib/modules/namedObject.inc:45 +#: ../lib/modules/namedObject.inc:44 msgid "Named object" msgstr "Namensobjekt" @@ -9283,7 +9286,7 @@ msgstr "Neues Feld" #: ../lib/types/kopanoDynamicGroupType.inc:164 ../lib/types/gon.inc:137 #: ../lib/types/gon.inc:201 ../lib/types/zarafaDynamicGroupType.inc:122 #: ../lib/types/zarafaDynamicGroupType.inc:178 ../lib/types/group.inc:174 -#: ../lib/types/group.inc:253 ../lib/types/netgroup.inc:119 +#: ../lib/types/group.inc:251 ../lib/types/netgroup.inc:119 #: ../lib/types/netgroup.inc:174 msgid "New group" msgstr "Neue Gruppe" @@ -9308,13 +9311,13 @@ msgstr "Neues Hauptpasswort wurde erfolgreich gesetzt." msgid "New object" msgstr "Neues Objekt" -#: ../templates/tools/ou_edit.php:205 ../help/help.inc:345 +#: ../templates/tools/ou_edit.php:205 ../help/help.inc:351 msgid "New organisational unit" msgstr "Neue Organisational Unit" -#: ../templates/config/confmain.php:505 ../lib/modules/sambaSamAccount.inc:2304 -#: ../lib/modules/posixAccount.inc:3203 ../lib/modules/windowsUser.inc:2912 -#: ../lib/modules/passwordSelfReset.inc:1296 +#: ../templates/config/confmain.php:520 ../lib/modules/sambaSamAccount.inc:2351 +#: ../lib/modules/posixAccount.inc:3162 ../lib/modules/windowsUser.inc:2884 +#: ../lib/modules/passwordSelfReset.inc:1294 msgid "New password" msgstr "Neues Passwort" @@ -9345,7 +9348,7 @@ msgstr "Neuer Profilname" msgid "New range" msgstr "Neuer Adressbereich" -#: ../lib/modules/nisMailAlias.inc:207 +#: ../lib/modules/nisMailAlias.inc:210 msgid "New recipient" msgstr "Neuer Empfänger" @@ -9353,7 +9356,7 @@ msgstr "Neuer Empfänger" msgid "New required attributes" msgstr "Neue erforderliche Attribute" -#: ../lib/types/gon.inc:208 ../lib/types/group.inc:260 +#: ../lib/types/gon.inc:208 ../lib/types/group.inc:258 msgid "New role" msgstr "Neue Rolle" @@ -9374,11 +9377,11 @@ msgstr "Neue Sudo-Rolle" msgid "New text area" msgstr "Neuer Textbereich" -#: ../lib/types/user.inc:188 ../lib/types/user.inc:674 +#: ../lib/types/user.inc:189 ../lib/types/user.inc:675 msgid "New user" msgstr "Neuer Benutzer" -#: ../lib/modules/inetOrgPerson.inc:1867 +#: ../lib/modules/inetOrgPerson.inc:1868 msgid "New user certificate" msgstr "Neues Benutzerzertifikat" @@ -9395,45 +9398,45 @@ msgstr "Neue Ansicht" msgid "New zone" msgstr "Neue Zone" -#: ../lib/modules/sambaDomain.inc:85 ../lib/modules/sambaDomain.inc:165 -#: ../lib/modules/sambaDomain.inc:186 ../lib/modules/sambaDomain.inc:361 -#: ../lib/modules/sambaDomain.inc:595 +#: ../lib/modules/sambaDomain.inc:84 ../lib/modules/sambaDomain.inc:164 +#: ../lib/modules/sambaDomain.inc:185 ../lib/modules/sambaDomain.inc:354 +#: ../lib/modules/sambaDomain.inc:592 msgid "Next RID" msgstr "Nächste RID" -#: ../lib/modules/sambaDomain.inc:212 ../lib/modules/sambaDomain.inc:213 +#: ../lib/modules/sambaDomain.inc:211 ../lib/modules/sambaDomain.inc:212 msgid "Next RID is not a number!" msgstr "Nächste RID ist keine Zahl!" -#: ../lib/modules/sambaDomain.inc:86 +#: ../lib/modules/sambaDomain.inc:85 msgid "Next RID to use when creating accounts (only used by Winbind)." msgstr "Nächste RID für neue Accounts (wird nur von Winbind verwendet)." -#: ../lib/modules/sambaDomain.inc:94 +#: ../lib/modules/sambaDomain.inc:93 msgid "Next RID to use when creating group accounts (only used by Winbind)." msgstr "Nächste RID für neue Gruppen (wird nur von Winbind verwendet)." -#: ../lib/modules/sambaDomain.inc:90 +#: ../lib/modules/sambaDomain.inc:89 msgid "Next RID to use when creating user accounts (only used by Winbind)." msgstr "Nächste RID für neue Benutzer (wird nur von Winbind verwendet)." -#: ../lib/modules/sambaDomain.inc:93 ../lib/modules/sambaDomain.inc:177 -#: ../lib/modules/sambaDomain.inc:188 ../lib/modules/sambaDomain.inc:377 -#: ../lib/modules/sambaDomain.inc:597 +#: ../lib/modules/sambaDomain.inc:92 ../lib/modules/sambaDomain.inc:176 +#: ../lib/modules/sambaDomain.inc:187 ../lib/modules/sambaDomain.inc:370 +#: ../lib/modules/sambaDomain.inc:594 msgid "Next group RID" msgstr "Nächste Gruppen-RID" -#: ../lib/modules/sambaDomain.inc:216 ../lib/modules/sambaDomain.inc:217 +#: ../lib/modules/sambaDomain.inc:215 ../lib/modules/sambaDomain.inc:216 msgid "Next group RID is not a number!" msgstr "Nächste Gruppen-RID ist keine Zahl!" -#: ../lib/modules/sambaDomain.inc:89 ../lib/modules/sambaDomain.inc:171 -#: ../lib/modules/sambaDomain.inc:187 ../lib/modules/sambaDomain.inc:369 -#: ../lib/modules/sambaDomain.inc:596 +#: ../lib/modules/sambaDomain.inc:88 ../lib/modules/sambaDomain.inc:170 +#: ../lib/modules/sambaDomain.inc:186 ../lib/modules/sambaDomain.inc:362 +#: ../lib/modules/sambaDomain.inc:593 msgid "Next user RID" msgstr "Nächste Benutzer-RID" -#: ../lib/modules/sambaDomain.inc:214 ../lib/modules/sambaDomain.inc:215 +#: ../lib/modules/sambaDomain.inc:213 ../lib/modules/sambaDomain.inc:214 msgid "Next user RID is not a number!" msgstr "Nächste Benutzer-RID ist keine Zahl!" @@ -9451,8 +9454,8 @@ msgstr "Spitznamen" #: ../lib/modules/qmailGroup.inc:286 ../lib/modules/qmailGroup.inc:287 #: ../lib/modules/qmailGroup.inc:288 ../lib/modules/qmailGroup.inc:328 #: ../lib/modules/qmailGroup.inc:329 ../lib/modules/qmailGroup.inc:330 -#: ../lib/modules/qmailGroup.inc:946 ../lib/modules/qmailGroup.inc:988 -#: ../lib/modules/qmailGroup.inc:994 ../lib/modules/imapAccess.inc:512 +#: ../lib/modules/qmailGroup.inc:797 ../lib/modules/qmailGroup.inc:839 +#: ../lib/modules/qmailGroup.inc:845 ../lib/modules/imapAccess.inc:512 msgid "No" msgstr "Nein" @@ -9478,12 +9481,12 @@ msgstr "Keine NIS-Objekte gefunden!" msgid "No RDN attribute was selected." msgstr "Es wurde kein RDN-Attribut gewählt." -#: ../lib/modules/sambaSamAccount.inc:1054 +#: ../lib/modules/sambaSamAccount.inc:1086 #: ../lib/modules/sambaGroupMapping.inc:316 msgid "No Samba 3 domains found in LDAP! Please create one first." msgstr "Keine Samba 3 Domänen gefunden, bitte erstellen Sie eine." -#: ../lib/modules/posixAccount.inc:490 ../lib/modules/posixAccount.inc:1563 +#: ../lib/modules/posixAccount.inc:495 ../lib/modules/posixAccount.inc:1541 msgid "No Unix groups found in LDAP! Please create one first." msgstr "Keine Unix-Gruppen gefunden, bitte erstellen Sie eine." @@ -9536,7 +9539,7 @@ msgstr "" msgid "No domains found!" msgstr "Keine Domänen gefunden!" -#: ../lib/modules/passwordSelfReset.inc:1820 +#: ../lib/modules/passwordSelfReset.inc:1818 msgid "No email address found." msgstr "Keine email-Adresse gefunden." @@ -9549,13 +9552,13 @@ msgstr "Keine Einträge gefunden!" msgid "No entry was selected to delete" msgstr "Kein Eintrag zum Löschen ausgewählt." -#: ../lib/modules/ldapPublicKey.inc:580 ../lib/modules/inetOrgPerson.inc:3544 -#: ../lib/modules/inetOrgPerson.inc:3581 ../lib/modules/customFields.inc:4599 +#: ../lib/modules/ldapPublicKey.inc:578 ../lib/modules/inetOrgPerson.inc:3545 +#: ../lib/modules/inetOrgPerson.inc:3582 ../lib/modules/customFields.inc:4599 msgid "No file received." msgstr "Keine Datei empfangen." #: ../templates/config/mainmanage.php:209 ../lib/modules/windowsUser.inc:1042 -#: ../lib/modules/ldapPublicKey.inc:130 ../lib/modules/inetOrgPerson.inc:104 +#: ../lib/modules/ldapPublicKey.inc:129 ../lib/modules/inetOrgPerson.inc:104 #: ../lib/modules/customFields.inc:4535 msgid "No file selected." msgstr "Keine Datei gewählt." @@ -9564,12 +9567,12 @@ msgstr "Keine Datei gewählt." msgid "No forwarding" msgstr "Keine Weiterleitung" -#: ../lib/modules/posixGroup.inc:754 ../lib/modules/posixAccount.inc:94 +#: ../lib/modules/posixGroup.inc:703 ../lib/modules/posixAccount.inc:94 msgid "No free ID-Number!" msgstr "Keine ID-Nummer mehr frei!" #: ../lib/types/kopanoDynamicGroupType.inc:163 ../lib/types/gon.inc:200 -#: ../lib/types/zarafaDynamicGroupType.inc:177 ../lib/types/group.inc:252 +#: ../lib/types/zarafaDynamicGroupType.inc:177 ../lib/types/group.inc:250 #: ../lib/types/netgroup.inc:173 msgid "No groups found!" msgstr "Keine Gruppen gefunden!" @@ -9594,14 +9597,14 @@ msgstr "Keine internen Attribute" msgid "No job runs found." msgstr "Keine Jobläufe gefunden." -#: ../templates/tests/lamdaemonTest.php:198 +#: ../templates/tests/lamdaemonTest.php:199 msgid "No lamdaemon path set, please update your LAM configuration settings." msgstr "" "Es wurde kein Pfad für den Lamdaemon gesetzt, bitte ändern Sie Ihre LAM-" "Einstellungen." #: ../templates/tests/lamdaemonTest.php:109 -#: ../templates/tests/lamdaemonTest.php:194 +#: ../templates/tests/lamdaemonTest.php:195 msgid "No lamdaemon server set, please update your LAM configuration settings." msgstr "" "Es wurde kein Server für den Lamdaemon gesetzt, bitte ändern Sie Ihre LAM-" @@ -9627,7 +9630,7 @@ msgstr "Keine neuen Attribute für diesen Eintrag verfügbar" msgid "No new entries" msgstr "Keine neuen Einträge" -#: ../templates/tools/multiEdit.php:340 ../lib/lists.inc:130 +#: ../templates/tools/multiEdit.php:342 ../lib/lists.inc:130 msgid "No objects found!" msgstr "Keine Objekte gefunden!" @@ -9635,8 +9638,8 @@ msgstr "Keine Objekte gefunden!" msgid "No or more than one base module selected!" msgstr "Es wurde kein oder mehrere Basismodule gewählt!" -#: ../templates/config/confsave.php:56 ../templates/config/confmain.php:78 -#: ../lib/modules/passwordSelfReset.inc:1422 +#: ../templates/config/confsave.php:56 ../templates/config/confmain.php:80 +#: ../lib/modules/passwordSelfReset.inc:1420 msgid "No password was entered!" msgstr "Passwort leer!" @@ -9664,11 +9667,11 @@ msgstr "Keine Auslieferung mit Programm" msgid "No quota" msgstr "Kein Quota" -#: ../lib/types/gon.inc:207 ../lib/types/group.inc:259 +#: ../lib/types/gon.inc:207 ../lib/types/group.inc:257 msgid "No roles found!" msgstr "Keine Rollen gefunden!" -#: ../lib/modules/customScripts.inc:200 +#: ../lib/modules/customScripts.inc:197 msgid "No scripts to run." msgstr "Keine Skripte zum Ausführen." @@ -9676,7 +9679,7 @@ msgstr "Keine Skripte zum Ausführen." msgid "No section text specified" msgstr "Kein Abschnittsname angegeben" -#: ../lib/modules/passwordSelfReset.inc:1459 +#: ../lib/modules/passwordSelfReset.inc:1457 msgid "No security answer found." msgstr "Keine Antwort auf Sicherheitsfrage gefunden." @@ -9710,7 +9713,7 @@ msgstr "Kein Eintrag gefunden." msgid "No sudo roles found!" msgstr "Keine Sudo-Rollen gefunden!" -#: ../lib/types/user.inc:673 +#: ../lib/types/user.inc:674 msgid "No users found!" msgstr "Keine Benutzer gefunden!" @@ -9722,20 +9725,20 @@ msgstr "Keine Ansichten gefunden!" msgid "Non-standard (\\n)" msgstr "Nicht-Standard (\\n)" -#: ../templates/config/confmain.php:461 -#: ../templates/selfService/adminMain.php:481 ../lib/modules/qmailUser.inc:58 +#: ../templates/config/confmain.php:465 +#: ../templates/selfService/adminMain.php:487 ../lib/modules/qmailUser.inc:58 msgid "None" msgstr "Keiner" #: ../lib/modules/sudoRole.inc:132 ../lib/modules/sudoRole.inc:194 #: ../lib/modules/sudoRole.inc:221 ../lib/modules/sudoRole.inc:280 -#: ../lib/modules/sudoRole.inc:480 ../lib/modules/sudoRole.inc:763 +#: ../lib/modules/sudoRole.inc:320 ../lib/modules/sudoRole.inc:470 msgid "Not after" msgstr "Nicht nach" #: ../lib/modules/sudoRole.inc:127 ../lib/modules/sudoRole.inc:200 #: ../lib/modules/sudoRole.inc:222 ../lib/modules/sudoRole.inc:282 -#: ../lib/modules/sudoRole.inc:473 ../lib/modules/sudoRole.inc:758 +#: ../lib/modules/sudoRole.inc:315 ../lib/modules/sudoRole.inc:465 msgid "Not before" msgstr "Nicht vor" @@ -9752,8 +9755,8 @@ msgstr "" "Objektklassen gefordert werden." #: ../lib/modules/windowsGroup.inc:127 ../lib/modules/windowsGroup.inc:184 -#: ../lib/modules/windowsGroup.inc:254 ../lib/modules/windowsGroup.inc:345 -#: ../lib/modules/windowsGroup.inc:897 +#: ../lib/modules/windowsGroup.inc:254 ../lib/modules/windowsGroup.inc:341 +#: ../lib/modules/windowsGroup.inc:879 msgid "Notes" msgstr "Notizen" @@ -9766,17 +9769,17 @@ msgid "Notice" msgstr "Hinweis" #: ../lib/passwordExpirationJob.inc:90 ../lib/passwordExpirationJob.inc:144 -#: ../help/help.inc:390 +#: ../help/help.inc:396 msgid "Notification period" msgstr "Benachrichtigungszeitraum" -#: ../lib/modules/qmailUser.inc:1321 ../lib/modules/windowsUser.inc:4021 -#: ../lib/modules/shadowAccount.inc:951 ../lib/modules/freeRadius.inc:885 +#: ../lib/modules/qmailUser.inc:1161 ../lib/modules/windowsUser.inc:3993 +#: ../lib/modules/shadowAccount.inc:978 ../lib/modules/freeRadius.inc:885 msgid "Notify users about account expiration" msgstr "Informiere Benutzer über Kontoablauf" -#: ../lib/modules/locking389ds.inc:443 ../lib/modules/ppolicyUser.inc:510 -#: ../lib/modules/windowsUser.inc:3874 ../lib/modules/shadowAccount.inc:848 +#: ../lib/modules/locking389ds.inc:459 ../lib/modules/ppolicyUser.inc:510 +#: ../lib/modules/windowsUser.inc:3846 ../lib/modules/shadowAccount.inc:875 msgid "Notify users about password expiration" msgstr "Informiere Benutzer über Passwortablauf" @@ -9819,13 +9822,13 @@ msgstr "Anzahl Tage nach denen das Konto abläuft." msgid "Number of failed logins." msgstr "Anzahl gescheiterter Anmeldungen." -#: ../lib/modules/sambaDomain.inc:138 +#: ../lib/modules/sambaDomain.inc:137 msgid "Number of minutes after which the bad logon attempts are reset." msgstr "" "Zeit in Minuten, nach der der Zähler für fehlerhafte Anmeldungen " "zurückgesetzt wird." -#: ../lib/modules/pykotaBillingCode.inc:90 +#: ../lib/modules/pykotaBillingCode.inc:89 msgid "Number of pages printed with this billing code." msgstr "Die Anzahl Seiten, die mit diesem Rechnungscode gedruckt wurden." @@ -9842,13 +9845,13 @@ msgstr "Anzahl Zeilen in der Textbox." msgid "Number of rules that must match" msgstr "Anzahl Regeln, die erfüllt sein müssen" -#: ../lib/modules/ppolicy.inc:85 ../lib/modules/sambaDomain.inc:126 +#: ../lib/modules/ppolicy.inc:84 ../lib/modules/sambaDomain.inc:125 msgid "" "Number of seconds after the user is allowed to change his password again." msgstr "" "Zeit in Sekunden nach der der Benutzer sein Passwort erneut ändern darf." -#: ../lib/modules/ppolicy.inc:89 ../lib/modules/sambaDomain.inc:130 +#: ../lib/modules/ppolicy.inc:88 ../lib/modules/sambaDomain.inc:129 msgid "Number of seconds after which the user must change his password." msgstr "Zeit in Sekunden nach der der Benutzer sein Passwort ändern muss." @@ -9876,14 +9879,14 @@ msgstr "OU ist ungültig!" msgid "OU is not empty or invalid!" msgstr "OU ist nicht leer oder ungültig!" -#: ../help/help.inc:345 ../help/help.inc:347 +#: ../help/help.inc:351 ../help/help.inc:353 msgid "OU-Editor" msgstr "OU-Editor" #: ../lib/modules/dynamicList.inc:54 ../lib/modules/dynamicList.inc:243 #: ../lib/modules/customBaseType.inc:49 ../lib/modules/customBaseType.inc:105 -#: ../lib/modules/yubiKeyUser.inc:96 ../lib/modules/yubiKeyUser.inc:574 -#: ../lib/modules/yubiKeyUser.inc:585 +#: ../lib/modules/yubiKeyUser.inc:96 ../lib/modules/yubiKeyUser.inc:571 +#: ../lib/modules/yubiKeyUser.inc:582 msgid "Object class" msgstr "Objektklasse" @@ -9905,26 +9908,26 @@ msgstr "Anzahl Objekte: %s" msgid "Obsolete" msgstr "Veraltet" -#: ../lib/modules/sambaDomain.inc:289 ../lib/modules/sambaDomain.inc:298 -#: ../lib/modules/sambaDomain.inc:307 ../lib/modules/sambaDomain.inc:608 -#: ../lib/modules/sambaDomain.inc:613 ../lib/modules/sambaDomain.inc:618 +#: ../lib/modules/sambaDomain.inc:282 ../lib/modules/sambaDomain.inc:291 +#: ../lib/modules/sambaDomain.inc:300 ../lib/modules/sambaDomain.inc:605 +#: ../lib/modules/sambaDomain.inc:610 ../lib/modules/sambaDomain.inc:615 msgid "Off" msgstr "Aus" #: ../lib/modules/windowsUser.inc:173 ../lib/modules/windowsUser.inc:490 #: ../lib/modules/windowsUser.inc:849 ../lib/modules/windowsUser.inc:942 -#: ../lib/modules/windowsUser.inc:1138 ../lib/modules/windowsUser.inc:2723 -#: ../lib/modules/windowsUser.inc:2926 ../lib/modules/inetOrgPerson.inc:163 +#: ../lib/modules/windowsUser.inc:1137 ../lib/modules/windowsUser.inc:2695 +#: ../lib/modules/windowsUser.inc:2898 ../lib/modules/inetOrgPerson.inc:163 #: ../lib/modules/inetOrgPerson.inc:469 ../lib/modules/inetOrgPerson.inc:504 #: ../lib/modules/inetOrgPerson.inc:733 ../lib/modules/inetOrgPerson.inc:737 #: ../lib/modules/inetOrgPerson.inc:1364 ../lib/modules/inetOrgPerson.inc:1367 -#: ../lib/modules/inetOrgPerson.inc:1969 ../lib/modules/inetOrgPerson.inc:2027 -#: ../lib/modules/inetOrgPerson.inc:2746 ../lib/modules/inetOrgPerson.inc:3866 -#: ../lib/modules/inetOrgPerson.inc:3907 +#: ../lib/modules/inetOrgPerson.inc:1970 ../lib/modules/inetOrgPerson.inc:2028 +#: ../lib/modules/inetOrgPerson.inc:2747 ../lib/modules/inetOrgPerson.inc:3867 +#: ../lib/modules/inetOrgPerson.inc:3908 msgid "Office name" msgstr "Büroname" -#: ../templates/misc/ajax.php:242 ../templates/misc/ajax.php:272 +#: ../templates/misc/ajax.php:273 ../templates/misc/ajax.php:303 #: ../templates/lists/changePassword.php:340 #: ../templates/lists/changePassword.php:377 #: ../templates/lists/changePassword.php:430 @@ -9947,28 +9950,27 @@ msgstr "Büroname" #: ../templates/profedit/profilemain.php:228 #: ../templates/profedit/profilemain.php:235 #: ../templates/profedit/profilemain.php:242 ../lib/types/automountType.inc:213 -#: ../lib/types/user.inc:356 ../lib/types/bind.inc:157 ../lib/html.inc:603 +#: ../lib/types/user.inc:357 ../lib/types/bind.inc:157 ../lib/html.inc:610 #: ../lib/modules/selfRegistration.inc:462 ../lib/modules/zarafaContact.inc:471 #: ../lib/modules/zarafaGroup.inc:486 ../lib/modules/device.inc:292 -#: ../lib/modules/kopanoGroup.inc:438 ../lib/modules/sambaSamAccount.inc:1392 -#: ../lib/modules/sambaSamAccount.inc:1524 ../lib/modules/zarafaUser.inc:975 -#: ../lib/modules/zarafaUser.inc:1026 ../lib/modules/kopanoContact.inc:424 -#: ../lib/modules/sambaGroupMapping.inc:485 ../lib/modules/nisnetgroup.inc:492 -#: ../lib/modules/nisnetgroup.inc:506 ../lib/modules/kopanoUser.inc:975 -#: ../lib/modules/kopanoUser.inc:1026 ../lib/modules/asteriskExtension.inc:463 -#: ../lib/modules/aliasEntry.inc:155 ../lib/modules/aliasEntry.inc:178 -#: ../lib/modules/qmailGroup.inc:836 ../lib/modules/imapAccess.inc:295 -#: ../lib/modules/organizationalRole.inc:325 -#: ../lib/modules/passwordSelfReset.inc:1082 -#: ../lib/modules/passwordSelfReset.inc:1329 -#: ../lib/modules/windowsGroup.inc:724 ../lib/modules/groupOfNames.inc:421 -#: ../lib/modules/groupOfNames.inc:570 ../lib/modules/nisMailAlias.inc:374 -#: ../lib/modules/nisMailAlias.inc:383 ../lib/lists.inc:817 -#: ../lib/lists.inc:900 ../lib/modules.inc:1007 ../lib/modules.inc:1307 +#: ../lib/modules/kopanoGroup.inc:438 ../lib/modules/sambaSamAccount.inc:1437 +#: ../lib/modules/sambaSamAccount.inc:1584 ../lib/modules/zarafaUser.inc:975 +#: ../lib/modules/zarafaUser.inc:1026 ../lib/modules/kopanoContact.inc:422 +#: ../lib/modules/sambaGroupMapping.inc:483 ../lib/modules/nisnetgroup.inc:492 +#: ../lib/modules/kopanoUser.inc:897 ../lib/modules/kopanoUser.inc:946 +#: ../lib/modules/asteriskExtension.inc:461 ../lib/modules/aliasEntry.inc:155 +#: ../lib/modules/aliasEntry.inc:178 ../lib/modules/qmailGroup.inc:688 +#: ../lib/modules/imapAccess.inc:295 ../lib/modules/organizationalRole.inc:325 +#: ../lib/modules/passwordSelfReset.inc:1080 +#: ../lib/modules/passwordSelfReset.inc:1327 +#: ../lib/modules/windowsGroup.inc:705 ../lib/modules/groupOfNames.inc:421 +#: ../lib/modules/groupOfNames.inc:570 ../lib/modules/nisMailAlias.inc:388 +#: ../lib/lists.inc:806 ../lib/lists.inc:891 ../lib/modules.inc:1007 +#: ../lib/modules.inc:1311 msgid "Ok" msgstr "Ok" -#: ../lib/modules/posixAccount.inc:3199 +#: ../lib/modules/posixAccount.inc:3158 msgid "Old password" msgstr "Altes Passwort" @@ -9977,16 +9979,16 @@ msgstr "Altes Passwort" msgid "Old value" msgstr "Alter Wert" -#: ../lib/modules/sambaDomain.inc:289 ../lib/modules/sambaDomain.inc:298 -#: ../lib/modules/sambaDomain.inc:307 ../lib/modules/sambaDomain.inc:609 -#: ../lib/modules/sambaDomain.inc:614 ../lib/modules/sambaDomain.inc:619 +#: ../lib/modules/sambaDomain.inc:282 ../lib/modules/sambaDomain.inc:291 +#: ../lib/modules/sambaDomain.inc:300 ../lib/modules/sambaDomain.inc:606 +#: ../lib/modules/sambaDomain.inc:611 ../lib/modules/sambaDomain.inc:616 msgid "On" msgstr "An" #: ../lib/modules/sambaSamAccount.inc:245 #: ../lib/modules/sambaSamAccount.inc:386 -#: ../lib/modules/sambaSamAccount.inc:1510 -#: ../lib/modules/sambaSamAccount.inc:1986 +#: ../lib/modules/sambaSamAccount.inc:1571 +#: ../lib/modules/sambaSamAccount.inc:2033 msgid "On broken or timed out connection" msgstr "Bei Trennung oder abgelaufenem Zeitlimit" @@ -10017,12 +10019,12 @@ msgstr "Befehlsstatistiken" msgid "Operation successful. DN %s has been created." msgstr "Operation erfolgreich. DN %s wurde erzeugt." -#: ../templates/tools/multiEdit.php:139 ../help/help.inc:355 +#: ../templates/tools/multiEdit.php:141 ../help/help.inc:361 msgid "Operations" msgstr "Operationen" -#: ../templates/config/confmain.php:495 -#: ../templates/selfService/adminMain.php:515 ../help/help.inc:308 +#: ../templates/config/confmain.php:510 +#: ../templates/selfService/adminMain.php:527 ../help/help.inc:312 msgid "Optional" msgstr "Optional" @@ -10035,23 +10037,23 @@ msgstr "Optionale Attribute" msgid "Optional description for the PC." msgstr "Hier können Sie eine optionale Beschreibung für den PC angeben." -#: ../lib/types/sudo.inc:86 ../lib/modules/mitKerberos.inc:399 +#: ../lib/types/sudo.inc:86 ../lib/modules/mitKerberos.inc:405 #: ../lib/modules/zarafaDynamicGroup.inc:251 -#: ../lib/modules/zarafaContact.inc:255 ../lib/modules/posixGroup.inc:579 +#: ../lib/modules/zarafaContact.inc:255 ../lib/modules/posixGroup.inc:528 #: ../lib/modules/zarafaGroup.inc:259 ../lib/modules/kopanoGroup.inc:238 -#: ../lib/modules/posixAccount.inc:2331 ../lib/modules/zarafaUser.inc:601 +#: ../lib/modules/posixAccount.inc:2289 ../lib/modules/zarafaUser.inc:601 #: ../lib/modules/kopanoContact.inc:240 ../lib/modules/kolabUser.inc:331 #: ../lib/modules/kopanoDynamicGroup.inc:214 ../lib/modules/sudoRole.inc:93 #: ../lib/modules/sudoRole.inc:122 ../lib/modules/sudoRole.inc:188 #: ../lib/modules/sudoRole.inc:220 ../lib/modules/sudoRole.inc:232 -#: ../lib/modules/sudoRole.inc:450 ../lib/modules/sudoRole.inc:752 -#: ../lib/modules/kopanoUser.inc:601 ../lib/modules/asteriskExtension.inc:123 +#: ../lib/modules/sudoRole.inc:310 ../lib/modules/sudoRole.inc:459 +#: ../lib/modules/kopanoUser.inc:566 ../lib/modules/asteriskExtension.inc:123 #: ../lib/modules/asteriskVoicemail.inc:114 #: ../lib/modules/asteriskVoicemail.inc:124 #: ../lib/modules/asteriskVoicemail.inc:141 #: ../lib/modules/asteriskVoicemail.inc:189 #: ../lib/modules/asteriskVoicemail.inc:256 -#: ../lib/modules/asteriskVoicemail.inc:373 ../lib/modules/qmailGroup.inc:576 +#: ../lib/modules/asteriskVoicemail.inc:373 ../lib/modules/qmailGroup.inc:475 #: ../lib/modules/heimdalKerberos.inc:331 msgid "Options" msgstr "Optionen" @@ -10065,7 +10067,7 @@ msgstr "" msgid "Or paste your LDIF here" msgstr "Oder geben Sie den LDIF-Text hier an" -#: ../lib/modules/oracleService.inc:47 +#: ../lib/modules/oracleService.inc:46 msgid "Oracle database" msgstr "Oracle-Datenbank" @@ -10079,7 +10081,7 @@ msgstr "Oracle-Datenbanken" #: ../lib/modules/sudoRole.inc:137 ../lib/modules/sudoRole.inc:206 #: ../lib/modules/sudoRole.inc:223 ../lib/modules/sudoRole.inc:284 -#: ../lib/modules/sudoRole.inc:483 ../lib/modules/sudoRole.inc:753 +#: ../lib/modules/sudoRole.inc:322 ../lib/modules/sudoRole.inc:460 msgid "Order" msgstr "Reihenfolge" @@ -10097,28 +10099,28 @@ msgstr "Sortierung" #: ../lib/modules/eduPerson.inc:552 ../lib/modules/windowsUser.inc:346 #: ../lib/modules/windowsUser.inc:350 ../lib/modules/windowsUser.inc:746 #: ../lib/modules/windowsUser.inc:785 ../lib/modules/windowsUser.inc:916 -#: ../lib/modules/windowsUser.inc:1196 ../lib/modules/windowsUser.inc:2749 -#: ../lib/modules/windowsUser.inc:3650 ../lib/modules/inetOrgPerson.inc:165 +#: ../lib/modules/windowsUser.inc:1195 ../lib/modules/windowsUser.inc:2721 +#: ../lib/modules/windowsUser.inc:3622 ../lib/modules/inetOrgPerson.inc:165 #: ../lib/modules/inetOrgPerson.inc:437 ../lib/modules/inetOrgPerson.inc:549 #: ../lib/modules/inetOrgPerson.inc:766 ../lib/modules/inetOrgPerson.inc:770 #: ../lib/modules/inetOrgPerson.inc:1509 ../lib/modules/inetOrgPerson.inc:1512 -#: ../lib/modules/inetOrgPerson.inc:1974 ../lib/modules/inetOrgPerson.inc:2051 -#: ../lib/modules/inetOrgPerson.inc:2867 ../lib/modules/inetOrgPerson.inc:3881 -#: ../lib/modules/inetOrgPerson.inc:3915 +#: ../lib/modules/inetOrgPerson.inc:1975 ../lib/modules/inetOrgPerson.inc:2052 +#: ../lib/modules/inetOrgPerson.inc:2868 ../lib/modules/inetOrgPerson.inc:3882 +#: ../lib/modules/inetOrgPerson.inc:3916 msgid "Organisation" msgstr "Organisation" #: ../templates/tools/ou_edit.php:219 ../lib/types/user.inc:110 #: ../lib/modules/windowsUser.inc:338 ../lib/modules/windowsUser.inc:342 #: ../lib/modules/windowsUser.inc:738 ../lib/modules/windowsUser.inc:782 -#: ../lib/modules/windowsUser.inc:913 ../lib/modules/windowsUser.inc:1193 -#: ../lib/modules/windowsUser.inc:2748 ../lib/modules/windowsUser.inc:3649 +#: ../lib/modules/windowsUser.inc:913 ../lib/modules/windowsUser.inc:1192 +#: ../lib/modules/windowsUser.inc:2720 ../lib/modules/windowsUser.inc:3621 #: ../lib/modules/inetOrgPerson.inc:165 ../lib/modules/inetOrgPerson.inc:429 #: ../lib/modules/inetOrgPerson.inc:552 ../lib/modules/inetOrgPerson.inc:758 #: ../lib/modules/inetOrgPerson.inc:762 ../lib/modules/inetOrgPerson.inc:1500 -#: ../lib/modules/inetOrgPerson.inc:1503 ../lib/modules/inetOrgPerson.inc:1973 -#: ../lib/modules/inetOrgPerson.inc:2048 ../lib/modules/inetOrgPerson.inc:2903 -#: ../lib/modules/inetOrgPerson.inc:3880 ../lib/modules/inetOrgPerson.inc:3915 +#: ../lib/modules/inetOrgPerson.inc:1503 ../lib/modules/inetOrgPerson.inc:1974 +#: ../lib/modules/inetOrgPerson.inc:2049 ../lib/modules/inetOrgPerson.inc:2904 +#: ../lib/modules/inetOrgPerson.inc:3881 ../lib/modules/inetOrgPerson.inc:3916 msgid "Organisational unit" msgstr "Organisatorische Einheit" @@ -10132,52 +10134,52 @@ msgstr "Organisatorische Einheiten" msgid "Organisational units contains an invalid entry." msgstr "Die organisatorischen Einheiten enthalten einen fehlerhaften Eintrag." -#: ../templates/tools/multiEdit.php:124 ../templates/tools/multiEdit.php:134 -#: ../templates/config/confmain.php:308 +#: ../templates/tools/multiEdit.php:126 ../templates/tools/multiEdit.php:136 +#: ../templates/config/confmain.php:312 msgid "Other" msgstr "Andere" #: ../lib/modules/windowsUser.inc:378 ../lib/modules/windowsUser.inc:382 #: ../lib/modules/windowsUser.inc:641 ../lib/modules/windowsUser.inc:934 -#: ../lib/modules/windowsUser.inc:1010 ../lib/modules/windowsUser.inc:1152 -#: ../lib/modules/windowsUser.inc:2735 ../lib/modules/windowsUser.inc:3632 +#: ../lib/modules/windowsUser.inc:1010 ../lib/modules/windowsUser.inc:1151 +#: ../lib/modules/windowsUser.inc:2707 ../lib/modules/windowsUser.inc:3604 msgid "Other mobiles" msgstr "Weitere Mobiltelefonnummern" #: ../lib/modules/windowsUser.inc:366 ../lib/modules/windowsUser.inc:370 #: ../lib/modules/windowsUser.inc:625 ../lib/modules/windowsUser.inc:928 -#: ../lib/modules/windowsUser.inc:1006 ../lib/modules/windowsUser.inc:1158 -#: ../lib/modules/windowsUser.inc:2733 ../lib/modules/windowsUser.inc:3634 +#: ../lib/modules/windowsUser.inc:1006 ../lib/modules/windowsUser.inc:1157 +#: ../lib/modules/windowsUser.inc:2705 ../lib/modules/windowsUser.inc:3606 msgid "Other pagers" msgstr "Weitere Pager" #: ../lib/modules/windowsUser.inc:165 ../lib/modules/windowsUser.inc:169 #: ../lib/modules/windowsUser.inc:514 ../lib/modules/windowsUser.inc:848 -#: ../lib/modules/windowsUser.inc:1002 ../lib/modules/windowsUser.inc:1147 -#: ../lib/modules/windowsUser.inc:2722 +#: ../lib/modules/windowsUser.inc:1002 ../lib/modules/windowsUser.inc:1146 +#: ../lib/modules/windowsUser.inc:2694 msgid "Other telephone numbers" msgstr "Weitere Telefonnummern" #: ../lib/modules/windowsUser.inc:205 ../lib/modules/windowsUser.inc:209 #: ../lib/modules/windowsUser.inc:526 ../lib/modules/windowsUser.inc:856 -#: ../lib/modules/windowsUser.inc:1164 ../lib/modules/windowsUser.inc:2737 +#: ../lib/modules/windowsUser.inc:1163 ../lib/modules/windowsUser.inc:2709 msgid "Other web sites" msgstr "Weitere Webseiten" -#: ../lib/modules/customScripts.inc:84 ../lib/modules/customScripts.inc:99 -#: ../lib/modules/customScripts.inc:290 +#: ../lib/modules/customScripts.inc:83 ../lib/modules/customScripts.inc:98 +#: ../lib/modules/customScripts.inc:295 msgid "Output may contain HTML" msgstr "Ausgaben dürfen HTML beinhalten" -#: ../lib/modules/customScripts.inc:270 ../lib/modules/customScripts.inc:563 +#: ../lib/modules/customScripts.inc:275 ../lib/modules/customScripts.inc:568 #, php-format msgid "Output of command \"%s\" with return code %s" msgstr "Ausgabe des Kommandos \"%s\" mit Returncode %s" #: ../lib/modules/pykotaUser.inc:147 ../lib/modules/pykotaUser.inc:177 #: ../lib/modules/pykotaUser.inc:230 ../lib/modules/pykotaUser.inc:240 -#: ../lib/modules/pykotaUser.inc:287 ../lib/modules/pykotaUser.inc:366 -#: ../lib/modules/pykotaUser.inc:915 +#: ../lib/modules/pykotaUser.inc:287 ../lib/modules/pykotaUser.inc:354 +#: ../lib/modules/pykotaUser.inc:897 msgid "Overcharge factor" msgstr "Überberechnungsfaktor" @@ -10201,7 +10203,7 @@ msgstr "Standardmäßige Quotaeinstellungen überschreiben." msgid "Overwrite" msgstr "Überschreiben" -#: ../templates/config/confmain.php:300 ../lib/types/asteriskExt.inc:99 +#: ../templates/config/confmain.php:304 ../lib/types/asteriskExt.inc:99 #: ../lib/types/asteriskExt.inc:161 msgid "Owner" msgstr "Besitzer" @@ -10230,7 +10232,7 @@ msgstr "P-Knoten (0x02)" msgid "PC name" msgstr "PC-Name" -#: ../lib/modules/windowsHost.inc:96 +#: ../lib/modules/windowsHost.inc:95 msgid "PC01" msgstr "PC01" @@ -10239,8 +10241,8 @@ msgstr "PC01" msgid "PDF editor" msgstr "PDF-Editor" -#: ../templates/upload/masscreate.php:305 ../lib/lists.inc:789 -#: ../help/help.inc:256 +#: ../templates/upload/masscreate.php:305 ../lib/lists.inc:778 +#: ../help/help.inc:258 msgid "PDF structure" msgstr "PDF-Struktur" @@ -10275,13 +10277,13 @@ msgstr "PIN+Token" #: ../lib/modules/zarafaUser.inc:330 ../lib/modules/zarafaUser.inc:677 #: ../lib/modules/zarafaUser.inc:1372 ../lib/modules/kopanoUser.inc:330 -#: ../lib/modules/kopanoUser.inc:677 ../lib/modules/kopanoUser.inc:1373 +#: ../lib/modules/kopanoUser.inc:635 ../lib/modules/kopanoUser.inc:1292 msgid "POP3" msgstr "POP3" #: ../templates/lists/changePassword.php:412 -#: ../templates/lists/changePassword.php:449 ../lib/types/user.inc:464 -#: ../lib/types/user.inc:503 ../lib/modules/ppolicyUser.inc:510 +#: ../templates/lists/changePassword.php:449 ../lib/types/user.inc:465 +#: ../lib/types/user.inc:504 ../lib/modules/ppolicyUser.inc:510 msgid "PPolicy" msgstr "PPolicy" @@ -10306,35 +10308,35 @@ msgstr "" "Zielnetzwerk." #: ../lib/types/pykotaBillingCodeType.inc:96 -#: ../lib/modules/pykotaBillingCode.inc:89 -#: ../lib/modules/pykotaBillingCode.inc:118 -#: ../lib/modules/pykotaBillingCode.inc:151 -#: ../lib/modules/pykotaBillingCode.inc:260 +#: ../lib/modules/pykotaBillingCode.inc:88 +#: ../lib/modules/pykotaBillingCode.inc:117 +#: ../lib/modules/pykotaBillingCode.inc:152 +#: ../lib/modules/pykotaBillingCode.inc:262 msgid "Page count" msgstr "Seitenzahl" -#: ../templates/selfService/adminMain.php:578 ../help/help.inc:288 +#: ../templates/selfService/adminMain.php:590 ../help/help.inc:290 msgid "Page header" msgstr "Seitenkopf" -#: ../templates/selfService/adminMain.php:402 +#: ../templates/selfService/adminMain.php:408 msgid "Page layout" msgstr "Seitenlayout" -#: ../templates/config/confmain.php:229 ../help/help.inc:196 +#: ../templates/config/confmain.php:233 ../help/help.inc:196 msgid "Paged results" msgstr "Seitenweise Ergebnisse" #: ../lib/modules/windowsUser.inc:362 ../lib/modules/windowsUser.inc:617 #: ../lib/modules/windowsUser.inc:925 ../lib/modules/windowsUser.inc:1004 -#: ../lib/modules/windowsUser.inc:1155 ../lib/modules/windowsUser.inc:2732 -#: ../lib/modules/windowsUser.inc:3633 ../lib/modules/inetOrgPerson.inc:80 +#: ../lib/modules/windowsUser.inc:1154 ../lib/modules/windowsUser.inc:2704 +#: ../lib/modules/windowsUser.inc:3605 ../lib/modules/inetOrgPerson.inc:80 #: ../lib/modules/inetOrgPerson.inc:162 ../lib/modules/inetOrgPerson.inc:379 #: ../lib/modules/inetOrgPerson.inc:522 ../lib/modules/inetOrgPerson.inc:654 #: ../lib/modules/inetOrgPerson.inc:658 ../lib/modules/inetOrgPerson.inc:1419 -#: ../lib/modules/inetOrgPerson.inc:1422 ../lib/modules/inetOrgPerson.inc:1960 -#: ../lib/modules/inetOrgPerson.inc:2636 ../lib/modules/inetOrgPerson.inc:3872 -#: ../lib/modules/inetOrgPerson.inc:3910 +#: ../lib/modules/inetOrgPerson.inc:1422 ../lib/modules/inetOrgPerson.inc:1961 +#: ../lib/modules/inetOrgPerson.inc:2637 ../lib/modules/inetOrgPerson.inc:3873 +#: ../lib/modules/inetOrgPerson.inc:3911 #: ../lib/modules/asteriskVoicemail.inc:110 #: ../lib/modules/asteriskVoicemail.inc:140 #: ../lib/modules/asteriskVoicemail.inc:183 @@ -10353,8 +10355,8 @@ msgid "Parent DN" msgstr "Eltern-DN" #: ../lib/modules/puppetClient.inc:95 ../lib/modules/puppetClient.inc:145 -#: ../lib/modules/puppetClient.inc:166 ../lib/modules/puppetClient.inc:279 -#: ../lib/modules/puppetClient.inc:404 ../lib/modules/puppetClient.inc:435 +#: ../lib/modules/puppetClient.inc:166 ../lib/modules/puppetClient.inc:276 +#: ../lib/modules/puppetClient.inc:400 ../lib/modules/puppetClient.inc:431 msgid "Parent node" msgstr "Elternknoten" @@ -10370,63 +10372,63 @@ msgstr "Elter von" msgid "Parse error" msgstr "Einlesefehler" -#: ../lib/types/user.inc:890 +#: ../lib/types/user.inc:891 msgid "Partially locked" msgstr "Teilweise gesperrt" #: ../lib/modules/pykotaPrinter.inc:106 ../lib/modules/pykotaPrinter.inc:165 #: ../lib/modules/pykotaPrinter.inc:195 ../lib/modules/pykotaPrinter.inc:242 -#: ../lib/modules/pykotaPrinter.inc:590 +#: ../lib/modules/pykotaPrinter.inc:582 msgid "Passthrough" msgstr "Durchlauf" #: ../templates/lists/changePassword.php:356 ../templates/login.php:359 #: ../templates/config/conflogin.php:121 -#: ../templates/selfService/selfServiceLogin.php:282 +#: ../templates/selfService/selfServiceLogin.php:283 #: ../templates/selfService/adminLogin.php:117 ../lib/selfService.inc:496 #: ../lib/modules/mitKerberos.inc:155 ../lib/modules/mitKerberos.inc:241 -#: ../lib/modules/mitKerberos.inc:256 ../lib/modules/mitKerberos.inc:812 -#: ../lib/modules/mitKerberos.inc:815 ../lib/modules/selfRegistration.inc:409 +#: ../lib/modules/mitKerberos.inc:256 ../lib/modules/mitKerberos.inc:834 +#: ../lib/modules/mitKerberos.inc:837 ../lib/modules/selfRegistration.inc:409 #: ../lib/modules/selfRegistration.inc:580 #: ../lib/modules/selfRegistration.inc:583 #: ../lib/modules/selfRegistration.inc:586 #: ../lib/modules/asteriskAccount.inc:114 -#: ../lib/modules/asteriskAccount.inc:323 ../lib/modules/posixGroup.inc:236 -#: ../lib/modules/posixGroup.inc:751 ../lib/modules/sambaSamAccount.inc:105 +#: ../lib/modules/asteriskAccount.inc:323 ../lib/modules/posixGroup.inc:219 +#: ../lib/modules/posixGroup.inc:700 ../lib/modules/sambaSamAccount.inc:105 #: ../lib/modules/sambaSamAccount.inc:106 #: ../lib/modules/sambaSamAccount.inc:252 #: ../lib/modules/sambaSamAccount.inc:433 ../lib/modules/posixAccount.inc:99 #: ../lib/modules/posixAccount.inc:100 ../lib/modules/posixAccount.inc:174 -#: ../lib/modules/posixAccount.inc:302 ../lib/modules/posixAccount.inc:422 -#: ../lib/modules/posixAccount.inc:1703 ../lib/modules/posixAccount.inc:2166 -#: ../lib/modules/posixAccount.inc:2169 ../lib/modules/posixAccount.inc:2256 -#: ../lib/modules/posixAccount.inc:2441 ../lib/modules/windowsUser.inc:243 +#: ../lib/modules/posixAccount.inc:307 ../lib/modules/posixAccount.inc:427 +#: ../lib/modules/posixAccount.inc:1684 ../lib/modules/posixAccount.inc:2124 +#: ../lib/modules/posixAccount.inc:2127 ../lib/modules/posixAccount.inc:2214 +#: ../lib/modules/posixAccount.inc:2399 ../lib/modules/windowsUser.inc:243 #: ../lib/modules/windowsUser.inc:418 ../lib/modules/windowsUser.inc:865 #: ../lib/modules/windowsUser.inc:951 ../lib/modules/windowsUser.inc:1026 -#: ../lib/modules/windowsUser.inc:1027 ../lib/modules/windowsUser.inc:2783 -#: ../lib/modules/windowsUser.inc:2786 ../lib/modules/inetOrgPerson.inc:782 -#: ../lib/modules/inetOrgPerson.inc:1562 ../lib/modules/inetOrgPerson.inc:1934 -#: ../lib/modules/inetOrgPerson.inc:1979 ../lib/modules/inetOrgPerson.inc:1982 -#: ../lib/modules/inetOrgPerson.inc:2190 ../lib/modules/inetOrgPerson.inc:3917 +#: ../lib/modules/windowsUser.inc:1027 ../lib/modules/windowsUser.inc:2755 +#: ../lib/modules/windowsUser.inc:2758 ../lib/modules/inetOrgPerson.inc:782 +#: ../lib/modules/inetOrgPerson.inc:1562 ../lib/modules/inetOrgPerson.inc:1935 +#: ../lib/modules/inetOrgPerson.inc:1980 ../lib/modules/inetOrgPerson.inc:1983 +#: ../lib/modules/inetOrgPerson.inc:2191 ../lib/modules/inetOrgPerson.inc:3918 #: ../lib/modules/asteriskVoicemail.inc:98 #: ../lib/modules/asteriskVoicemail.inc:164 #: ../lib/modules/customFields.inc:3020 ../lib/modules/heimdalKerberos.inc:123 #: ../lib/modules/heimdalKerberos.inc:210 #: ../lib/modules/heimdalKerberos.inc:222 #: ../lib/modules/heimdalKerberos.inc:718 -#: ../lib/modules/heimdalKerberos.inc:721 ../lib/modules.inc:1113 -#: ../help/help.inc:254 +#: ../lib/modules/heimdalKerberos.inc:721 ../lib/modules.inc:1114 +#: ../help/help.inc:256 msgid "Password" msgstr "Passwort" #: ../lib/modules/sambaSamAccount.inc:296 -#: ../lib/modules/sambaSamAccount.inc:1106 ../lib/modules/windowsUser.inc:234 +#: ../lib/modules/sambaSamAccount.inc:1138 ../lib/modules/windowsUser.inc:234 #: ../lib/modules/windowsUser.inc:562 ../lib/modules/windowsUser.inc:863 -#: ../lib/modules/windowsUser.inc:2771 ../lib/modules/windowsUser.inc:2822 +#: ../lib/modules/windowsUser.inc:2743 ../lib/modules/windowsUser.inc:2794 msgid "Password change at next login" msgstr "Passwortänderung beim nächsten Login" -#: ../lib/modules/mitKerberos.inc:147 ../lib/modules/mitKerberos.inc:1201 +#: ../lib/modules/mitKerberos.inc:147 ../lib/modules/mitKerberos.inc:1223 #: ../lib/modules/heimdalKerberos.inc:117 #: ../lib/modules/heimdalKerberos.inc:1056 msgid "Password change command" @@ -10441,23 +10443,23 @@ msgstr "Passwortänderungsoptionen" msgid "Password change required" msgstr "Passwortänderung erforderlich" -#: ../lib/types/ppolicyType.inc:94 ../lib/modules/ppolicy.inc:136 -#: ../lib/modules/ppolicy.inc:156 ../lib/modules/ppolicy.inc:295 -#: ../lib/modules/ppolicy.inc:509 +#: ../lib/types/ppolicyType.inc:94 ../lib/modules/ppolicy.inc:135 +#: ../lib/modules/ppolicy.inc:155 ../lib/modules/ppolicy.inc:288 +#: ../lib/modules/ppolicy.inc:502 msgid "Password change requires old password" msgstr "Passwortänderung benötigt altes Passwort" -#: ../lib/modules/mitKerberos.inc:188 ../lib/modules/mitKerberos.inc:458 +#: ../lib/modules/mitKerberos.inc:188 ../lib/modules/mitKerberos.inc:464 #: ../lib/modules/heimdalKerberos.inc:153 #: ../lib/modules/heimdalKerberos.inc:385 msgid "Password change service" msgstr "Passwortänderungsservice" -#: ../lib/modules/posixAccount.inc:188 ../lib/modules/posixAccount.inc:442 +#: ../lib/modules/posixAccount.inc:188 ../lib/modules/posixAccount.inc:447 msgid "Password change with old password" msgstr "Passwortänderung mit altem Passwort" -#: ../lib/modules/posixAccount.inc:3389 +#: ../lib/modules/posixAccount.inc:3348 msgid "Password changed." msgstr "Passwort geändert." @@ -10466,7 +10468,7 @@ msgid "Password check" msgstr "Passwortprüfung" #: ../lib/modules/mitKerberos.inc:288 ../lib/modules/selfRegistration.inc:583 -#: ../lib/modules/posixGroup.inc:751 ../lib/modules/sambaSamAccount.inc:106 +#: ../lib/modules/posixGroup.inc:700 ../lib/modules/sambaSamAccount.inc:106 #: ../lib/modules/sambaSamAccount.inc:107 ../lib/modules/posixAccount.inc:100 #: ../lib/modules/posixAccount.inc:101 ../lib/modules/windowsUser.inc:1027 #: ../lib/modules/inetOrgPerson.inc:109 @@ -10476,20 +10478,20 @@ msgstr "Passwort enthält ungültige Zeichen. Gültige Zeichen sind:" #: ../lib/modules/sambaSamAccount.inc:281 #: ../lib/modules/sambaSamAccount.inc:284 #: ../lib/modules/sambaSamAccount.inc:455 -#: ../lib/modules/sambaSamAccount.inc:1096 -#: ../lib/modules/sambaSamAccount.inc:1705 ../lib/modules/windowsUser.inc:220 +#: ../lib/modules/sambaSamAccount.inc:1128 +#: ../lib/modules/sambaSamAccount.inc:1752 ../lib/modules/windowsUser.inc:220 #: ../lib/modules/windowsUser.inc:540 ../lib/modules/windowsUser.inc:859 -#: ../lib/modules/windowsUser.inc:1222 ../lib/modules/windowsUser.inc:2760 +#: ../lib/modules/windowsUser.inc:1222 ../lib/modules/windowsUser.inc:2732 msgid "Password does not expire" msgstr "Passwort läuft nicht ab" -#: ../lib/types/user.inc:113 ../lib/types/user.inc:368 -#: ../lib/types/user.inc:1078 ../lib/modules/locking389ds.inc:65 -#: ../lib/modules/locking389ds.inc:77 ../lib/modules/locking389ds.inc:123 -#: ../lib/modules/locking389ds.inc:391 ../lib/modules/shadowAccount.inc:60 +#: ../lib/types/user.inc:113 ../lib/types/user.inc:369 +#: ../lib/types/user.inc:1079 ../lib/modules/locking389ds.inc:65 +#: ../lib/modules/locking389ds.inc:77 ../lib/modules/locking389ds.inc:124 +#: ../lib/modules/locking389ds.inc:407 ../lib/modules/shadowAccount.inc:60 #: ../lib/modules/shadowAccount.inc:117 ../lib/modules/shadowAccount.inc:182 -#: ../lib/modules/shadowAccount.inc:216 ../lib/modules/shadowAccount.inc:328 -#: ../lib/modules/shadowAccount.inc:573 +#: ../lib/modules/shadowAccount.inc:216 ../lib/modules/shadowAccount.inc:343 +#: ../lib/modules/shadowAccount.inc:600 msgid "Password expiration" msgstr "Passwortablauf" @@ -10497,11 +10499,11 @@ msgstr "Passwortablauf" msgid "Password expiration must be are natural number or -1." msgstr "Passwortablauf muss eine natürliche Zahl oder -1 sein." -#: ../lib/types/user.inc:343 ../lib/types/user.inc:1128 +#: ../lib/types/user.inc:344 ../lib/types/user.inc:1129 msgid "Password expired" msgstr "Passwort abgelaufen" -#: ../templates/selfService/adminMain.php:555 ../help/help.inc:292 +#: ../templates/selfService/adminMain.php:567 ../help/help.inc:294 msgid "Password field label" msgstr "Passwortfeldbezeichner" @@ -10509,34 +10511,34 @@ msgstr "Passwortfeldbezeichner" msgid "Password for voicemail mailbox." msgstr "Passwort für den Anrufbeantworter." -#: ../lib/modules/posixGroup.inc:493 ../lib/modules/posixGroup.inc:586 -#: ../lib/modules/posixAccount.inc:185 ../lib/modules/posixAccount.inc:335 -#: ../lib/modules/posixAccount.inc:2333 ../lib/modules/inetOrgPerson.inc:753 -#: ../lib/modules/inetOrgPerson.inc:3850 ../lib/modules/customFields.inc:149 +#: ../lib/modules/posixGroup.inc:442 ../lib/modules/posixGroup.inc:535 +#: ../lib/modules/posixAccount.inc:185 ../lib/modules/posixAccount.inc:340 +#: ../lib/modules/posixAccount.inc:2291 ../lib/modules/inetOrgPerson.inc:753 +#: ../lib/modules/inetOrgPerson.inc:3851 ../lib/modules/customFields.inc:149 #: ../lib/modules/customFields.inc:3036 msgid "Password hash type" msgstr "Passwort-Hash" #: ../lib/modules/sambaSamAccount.inc:410 -#: ../lib/modules/sambaSamAccount.inc:1888 -#: ../lib/modules/sambaSamAccount.inc:2277 +#: ../lib/modules/sambaSamAccount.inc:1935 +#: ../lib/modules/sambaSamAccount.inc:2324 msgid "Password history" msgstr "Passworthistorie" -#: ../lib/types/ppolicyType.inc:83 ../lib/modules/ppolicy.inc:92 -#: ../lib/modules/ppolicy.inc:145 ../lib/modules/ppolicy.inc:169 -#: ../lib/modules/ppolicy.inc:227 ../lib/modules/ppolicy.inc:473 -#: ../lib/modules/sambaDomain.inc:105 ../lib/modules/sambaDomain.inc:191 -#: ../lib/modules/sambaDomain.inc:283 ../lib/modules/sambaDomain.inc:600 +#: ../lib/types/ppolicyType.inc:83 ../lib/modules/ppolicy.inc:91 +#: ../lib/modules/ppolicy.inc:144 ../lib/modules/ppolicy.inc:168 +#: ../lib/modules/ppolicy.inc:220 ../lib/modules/ppolicy.inc:466 +#: ../lib/modules/sambaDomain.inc:104 ../lib/modules/sambaDomain.inc:190 +#: ../lib/modules/sambaDomain.inc:276 ../lib/modules/sambaDomain.inc:597 msgid "Password history length" msgstr "Passwort-Historienlänge" -#: ../templates/config/confmain.php:351 +#: ../templates/config/confmain.php:355 msgid "Password mail settings" msgstr "Passwortmaileinstellungen" #: ../lib/modules/shadowAccount.inc:58 ../lib/modules/shadowAccount.inc:59 -#: ../lib/modules/ppolicy.inc:167 ../lib/modules/sambaDomain.inc:222 +#: ../lib/modules/ppolicy.inc:166 ../lib/modules/sambaDomain.inc:221 msgid "Password maximum age must be are natural number." msgstr "Maximales Passwortalter muss eine natürliche Zahl sein." @@ -10544,13 +10546,13 @@ msgstr "Maximales Passwortalter muss eine natürliche Zahl sein." msgid "Password maximum age must be bigger as password minimum age." msgstr "Maximales Passwortalter muss größer als minimales Passwortalter sein." -#: ../lib/modules/shadowAccount.inc:64 ../lib/modules/ppolicy.inc:168 -#: ../lib/modules/sambaDomain.inc:220 +#: ../lib/modules/shadowAccount.inc:64 ../lib/modules/ppolicy.inc:167 +#: ../lib/modules/sambaDomain.inc:219 msgid "Password maximum age must be bigger than password minimum age." msgstr "Maximales Passwortalter muss größer als minimales Passwortalter sein." #: ../lib/modules/shadowAccount.inc:56 ../lib/modules/shadowAccount.inc:57 -#: ../lib/modules/ppolicy.inc:166 ../lib/modules/sambaDomain.inc:221 +#: ../lib/modules/ppolicy.inc:165 ../lib/modules/sambaDomain.inc:220 msgid "Password minimum age must be are natural number." msgstr "Minimales Passwortalter muss eine natürliche Zahl sein." @@ -10574,41 +10576,41 @@ msgstr "Passwortrichtlinien" msgid "Password policies (ppolicy)" msgstr "Passwortrichtlinien (ppolicy)" -#: ../templates/config/mainmanage.php:389 ../lib/types/user.inc:318 -#: ../lib/types/user.inc:1102 ../lib/modules/ppolicyUser.inc:44 +#: ../templates/config/mainmanage.php:389 ../lib/types/user.inc:319 +#: ../lib/types/user.inc:1103 ../lib/modules/ppolicyUser.inc:44 #: ../lib/modules/ppolicyUser.inc:55 ../lib/modules/ppolicyUser.inc:71 #: ../lib/modules/ppolicyUser.inc:87 ../lib/modules/ppolicyUser.inc:127 #: ../lib/modules/ppolicyUser.inc:255 ../lib/modules/ppolicyUser.inc:287 -#: ../lib/modules/ppolicy.inc:62 ../lib/modules/sambaDomain.inc:266 +#: ../lib/modules/ppolicy.inc:61 ../lib/modules/sambaDomain.inc:259 #: ../help/help.inc:162 msgid "Password policy" msgstr "Passwortrichtlinie" -#: ../lib/types/ppolicyType.inc:84 ../lib/modules/ppolicy.inc:96 -#: ../lib/modules/ppolicy.inc:146 ../lib/modules/ppolicy.inc:239 -#: ../lib/modules/ppolicy.inc:489 +#: ../lib/types/ppolicyType.inc:84 ../lib/modules/ppolicy.inc:95 +#: ../lib/modules/ppolicy.inc:145 ../lib/modules/ppolicy.inc:232 +#: ../lib/modules/ppolicy.inc:482 msgid "Password quality check" msgstr "Passwortqualitätsprüfung" -#: ../lib/modules/passwordSelfReset.inc:666 +#: ../lib/modules/passwordSelfReset.inc:664 msgid "Password reset confirmation" msgstr "Bestätigung für Passwortrücksetzung" -#: ../lib/modules/passwordSelfReset.inc:682 +#: ../lib/modules/passwordSelfReset.inc:680 msgid "Password reset notification" msgstr "Meldung über Passwortrücksetzung" -#: ../templates/config/confmain.php:319 +#: ../templates/config/confmain.php:323 msgid "Password reset page settings" msgstr "Einstellungen der Passwortrücksetzungsseite" #: ../lib/modules/passwordSelfReset.inc:65 -#: ../lib/modules/passwordSelfReset.inc:1007 -#: ../lib/modules/passwordSelfReset.inc:1229 +#: ../lib/modules/passwordSelfReset.inc:1005 +#: ../lib/modules/passwordSelfReset.inc:1227 msgid "Password self reset" msgstr "Passwortrücksetzung" -#: ../lib/modules/passwordSelfReset.inc:1881 +#: ../lib/modules/passwordSelfReset.inc:1879 msgid "Password successfully reset." msgstr "Passwort erfolgreich geändert." @@ -10618,8 +10620,8 @@ msgstr "Passwort zum Entsperren des SSH-Schlüssels." #: ../lib/modules/shadowAccount.inc:62 ../lib/modules/shadowAccount.inc:113 #: ../lib/modules/shadowAccount.inc:169 ../lib/modules/shadowAccount.inc:178 -#: ../lib/modules/shadowAccount.inc:210 ../lib/modules/shadowAccount.inc:320 -#: ../lib/modules/shadowAccount.inc:572 +#: ../lib/modules/shadowAccount.inc:210 ../lib/modules/shadowAccount.inc:340 +#: ../lib/modules/shadowAccount.inc:599 msgid "Password warning" msgstr "Passwortwarnung" @@ -10627,9 +10629,9 @@ msgstr "Passwortwarnung" msgid "Password warning must be are natural number." msgstr "Passwortwarnung muss eine natürliche Zahl sein." -#: ../templates/config/confmain.php:735 +#: ../templates/config/confmain.php:756 #: ../templates/3rdParty/pla/htdocs/password_checker.php:61 -#: ../lib/modules/passwordSelfReset.inc:1427 ../lib/modules.inc:1210 +#: ../lib/modules/passwordSelfReset.inc:1425 ../lib/modules.inc:1211 msgid "Passwords are different!" msgstr "Die Passwörter stimmen nicht überein!" @@ -10673,7 +10675,7 @@ msgstr "" "leer wird Passwortauthentifizierung verwendet (Passwort des in LAM " "angemeldeten Benutzers)." -#: ../templates/config/confmain.php:279 +#: ../templates/config/confmain.php:283 msgid "Path to external script" msgstr "Pfad zum externen Script" @@ -10698,7 +10700,7 @@ msgstr "" "Heimatverzeichnis des Benutzers." #: ../lib/modules/pykotaUser.inc:155 ../lib/modules/pykotaUser.inc:289 -#: ../lib/modules/pykotaUser.inc:333 +#: ../lib/modules/pykotaUser.inc:368 msgid "Payment" msgstr "Zahlung" @@ -10711,8 +10713,8 @@ msgid "Payment and job history for this user." msgstr "Zahlungs- und Jobhistorie für diesen Benutzer." #: ../lib/modules/pykotaUser.inc:242 ../lib/modules/pykotaUser.inc:251 -#: ../lib/modules/pykotaUser.inc:348 ../lib/modules/pykotaUser.inc:556 -#: ../lib/modules/pykotaUser.inc:1001 +#: ../lib/modules/pykotaUser.inc:336 ../lib/modules/pykotaUser.inc:560 +#: ../lib/modules/pykotaUser.inc:983 msgid "Payment history" msgstr "Zahlungshistorie" @@ -10729,8 +10731,8 @@ msgstr "Sollen auch alle Untereinträge gelöscht werden?" #: ../lib/modules/asteriskAccount.inc:279 #: ../lib/modules/asteriskAccount.inc:420 #: ../lib/modules/asteriskAccount.inc:676 -#: ../lib/modules/asteriskAccount.inc:996 -#: ../lib/modules/asteriskAccount.inc:1240 +#: ../lib/modules/asteriskAccount.inc:997 +#: ../lib/modules/asteriskAccount.inc:1241 msgid "Permit" msgstr "Erlauben" @@ -10742,15 +10744,15 @@ msgstr "Persönlich" msgid "Personal data" msgstr "Persönliche Daten" -#: ../lib/types/user.inc:105 ../lib/modules/windowsUser.inc:3640 -#: ../lib/modules/windowsUser.inc:3672 ../lib/modules/inetOrgPerson.inc:161 -#: ../lib/modules/inetOrgPerson.inc:561 ../lib/modules/inetOrgPerson.inc:2492 -#: ../lib/modules/inetOrgPerson.inc:2770 ../lib/modules/inetOrgPerson.inc:3886 -#: ../lib/modules/inetOrgPerson.inc:3917 ../lib/modules/inetOrgPerson.inc:3928 +#: ../lib/types/user.inc:105 ../lib/modules/windowsUser.inc:3612 +#: ../lib/modules/windowsUser.inc:3644 ../lib/modules/inetOrgPerson.inc:161 +#: ../lib/modules/inetOrgPerson.inc:561 ../lib/modules/inetOrgPerson.inc:2493 +#: ../lib/modules/inetOrgPerson.inc:2771 ../lib/modules/inetOrgPerson.inc:3887 +#: ../lib/modules/inetOrgPerson.inc:3918 ../lib/modules/inetOrgPerson.inc:3928 msgid "Photo" msgstr "Foto" -#: ../lib/modules/windowsUser.inc:1996 ../lib/modules/inetOrgPerson.inc:1692 +#: ../lib/modules/windowsUser.inc:1975 ../lib/modules/inetOrgPerson.inc:1693 msgid "Photo file" msgstr "Fotodatei" @@ -10758,8 +10760,8 @@ msgstr "Fotodatei" #: ../lib/modules/asteriskAccount.inc:280 #: ../lib/modules/asteriskAccount.inc:427 #: ../lib/modules/asteriskAccount.inc:680 -#: ../lib/modules/asteriskAccount.inc:997 -#: ../lib/modules/asteriskAccount.inc:1241 +#: ../lib/modules/asteriskAccount.inc:998 +#: ../lib/modules/asteriskAccount.inc:1242 msgid "Pickup group" msgstr "Pickup-Gruppe" @@ -10785,7 +10787,7 @@ msgstr "" msgid "Please activate the mail aliases type for this server profile." msgstr "Bitte aktivieren Sie den Mailaliastyp für dieses Serverprofil." -#: ../lib/modules/customScripts.inc:85 +#: ../lib/modules/customScripts.inc:84 msgid "" "Please activate this option if your scripts may generate HTML output. " "Otherwise, the output is treated as plain text." @@ -10813,7 +10815,7 @@ msgstr "" msgid "Please check if this email should be sent." msgstr "Bitte prüfen Sie ob diese email gesendet werden soll." -#: ../lib/modules/sambaSamAccount.inc:1044 +#: ../lib/modules/sambaSamAccount.inc:1076 msgid "Please check your settings on the Unix page!" msgstr "Bitte überprüfen Sie Ihre Eingaben auf der Unix-Seite!" @@ -10826,11 +10828,11 @@ msgstr "" "ist \"mail \" aber Sie können auch \"uid\" oder \"userPrincipalName\" " "verwenden." -#: ../lib/modules/passwordSelfReset.inc:668 +#: ../lib/modules/passwordSelfReset.inc:666 msgid "Please click here to continue: @@resetLink@@" msgstr "Bitte klicken Sie zum Fortfahren hier: @@resetLink@@" -#: ../lib/types/user.inc:349 +#: ../lib/types/user.inc:350 msgid "Please click to lock/unlock this account." msgstr "Zum (Ent)Sperren bitte klicken." @@ -10838,7 +10840,7 @@ msgstr "Zum (Ent)Sperren bitte klicken." msgid "Please create a profile by clicking on the link below." msgstr "Bitte erstellen Sie ein Profil indem Sie auf den untigen Link klicken." -#: ../lib/lists.inc:1258 +#: ../lib/lists.inc:1257 msgid "" "Please do not edit multiple accounts in parallel in multiple browser tabs." msgstr "" @@ -10860,7 +10862,7 @@ msgstr "Bitte füllen Sie erst die allgemeinen Captcha-Einstellungen aus." msgid "Please enter \"Yes\" or \"No\"." msgstr "Bitte geben Sie entweder \"Ja\" oder \"Nein\" ein." -#: ../templates/selfService/adminMain.php:253 +#: ../templates/selfService/adminMain.php:259 msgid "Please enter a 2-factor base URL." msgstr "Bitte geben Sie eine Basis-URL für die 2-Faktor-Authentifizierung ein." @@ -10873,7 +10875,7 @@ msgstr "" msgid "Please enter a caller ID." msgstr "Bitte geben Sie eine Anrufer-ID ein." -#: ../help/help.inc:366 +#: ../help/help.inc:372 msgid "" "Please enter a comma separated list of attributes to export. Using \"*\" " "will export all attributes." @@ -10915,7 +10917,7 @@ msgid "Please enter a descriptive label for this field." msgstr "Hier können Sie eine Beschreibung für dieses Feld eintragen." #: ../lib/modules/nisObject.inc:62 ../lib/modules/automount.inc:64 -#: ../lib/modules/sudoRole.inc:66 ../lib/modules/oracleService.inc:62 +#: ../lib/modules/sudoRole.inc:66 ../lib/modules/oracleService.inc:61 msgid "Please enter a descriptive name for this entry." msgstr "Hier können Sie eine Beschreibung für diesen Eintrag eintragen." @@ -10923,7 +10925,7 @@ msgstr "Hier können Sie eine Beschreibung für diesen Eintrag eintragen." msgid "Please enter a descriptive text for this group." msgstr "Hier können Sie eine Beschreibung für diese Gruppe eintragen." -#: ../lib/modules/windowsHost.inc:79 +#: ../lib/modules/windowsHost.inc:78 msgid "Please enter a descriptive text for this host." msgstr "Hier können Sie eine Beschreibung für diesen Host eintragen." @@ -10961,12 +10963,12 @@ msgstr "Bitte geben Sie einen Namen für das Gerät ein." msgid "Please enter a name for this folder." msgstr "Bitte geben Sie einen Namen für diesen Ordner ein." -#: ../lib/modules/ppolicy.inc:165 +#: ../lib/modules/ppolicy.inc:164 msgid "Please enter a name for this policy." msgstr "" "Bitte geben Sie einen eindeutigen Namen für diese Passwortrichtlinie ein." -#: ../lib/modules/nsview.inc:69 +#: ../lib/modules/nsview.inc:68 msgid "Please enter a name for your view." msgstr "Bitte geben Sie einen Namen für die Ansicht ein." @@ -11013,8 +11015,8 @@ msgstr "Bitte geben Sie eine Zahl für die Größenbeschränkung ein." #: ../lib/modules/bindDLZXfr.inc:94 ../lib/modules/courierMailAccount.inc:212 #: ../lib/modules/bindDLZ.inc:561 ../lib/modules/windowsUser.inc:1033 #: ../lib/modules/kolabUser.inc:221 ../lib/modules/kolabUser.inc:222 -#: ../lib/modules/inetOrgPerson.inc:2513 ../lib/modules/inetOrgPerson.inc:2516 -#: ../lib/modules/inetOrgPerson.inc:2519 ../lib/modules/sudoRole.inc:284 +#: ../lib/modules/inetOrgPerson.inc:2514 ../lib/modules/inetOrgPerson.inc:2517 +#: ../lib/modules/inetOrgPerson.inc:2520 ../lib/modules/sudoRole.inc:284 #: ../lib/modules/sudoRole.inc:285 ../lib/modules/kopanoUser.inc:431 #: ../lib/modules/kopanoUser.inc:432 ../lib/modules/kopanoUser.inc:433 #: ../lib/modules/kopanoUser.inc:434 ../lib/modules/kopanoUser.inc:435 @@ -11023,15 +11025,15 @@ msgstr "Bitte geben Sie eine Zahl für die Größenbeschränkung ein." msgid "Please enter a number." msgstr "Bitte geben Sie einen Zahl ein." -#: ../lib/modules/ppolicy.inc:174 +#: ../lib/modules/ppolicy.inc:173 msgid "Please enter a numeric value for the expire warning." msgstr "Bitte geben Sie eine Zahl für die Ablaufwarnung an." -#: ../lib/modules/ppolicy.inc:173 +#: ../lib/modules/ppolicy.inc:172 msgid "Please enter a numeric value for the failure count interval." msgstr "Bitte geben Sie eine Zahl für das Fehlerzählerintervall ein!" -#: ../lib/modules/ppolicy.inc:170 +#: ../lib/modules/ppolicy.inc:169 msgid "Please enter a numeric value for the grace authentication limit." msgstr "Bitte geben Sie eine Zahl für das Aufschublimit ein!" @@ -11039,19 +11041,19 @@ msgstr "Bitte geben Sie eine Zahl für das Aufschublimit ein!" msgid "Please enter a numeric value for the idle timeout." msgstr "Bitte geben Sie eine Zahl für das Leerlauflimit ein." -#: ../lib/modules/ppolicy.inc:171 +#: ../lib/modules/ppolicy.inc:170 msgid "Please enter a numeric value for the lockout duration." msgstr "Bitte geben Sie eine Zahl für die Sperrzeit ein." -#: ../lib/modules/ppolicy.inc:172 +#: ../lib/modules/ppolicy.inc:171 msgid "Please enter a numeric value for the maximum failure count." msgstr "Bitte geben Sie eine Zahl für die maximale Fehlerzahl ein." -#: ../lib/modules/ppolicy.inc:175 +#: ../lib/modules/ppolicy.inc:174 msgid "Please enter a numeric value for the minimum password length." msgstr "Bitte geben Sie eine Zahl für die minimale Passwortlänge ein." -#: ../lib/modules/ppolicy.inc:169 +#: ../lib/modules/ppolicy.inc:168 msgid "Please enter a numeric value for the password history length." msgstr "Bitte geben Sie eine Zahl für die Länge der Passworthistorie ein." @@ -11067,7 +11069,7 @@ msgstr "Bitte geben Sie eine LDAP-Abfrage ein." msgid "Please enter a role name." msgstr "Bitte geben Sie einen Rollennamen ein." -#: ../lib/modules/passwordSelfReset.inc:1463 +#: ../lib/modules/passwordSelfReset.inc:1461 msgid "Please enter a security answer." msgstr "Bitte geben Sie eine Antwort auf die Sicherheitsfrage ein." @@ -11091,7 +11093,7 @@ msgstr "" msgid "Please enter a unique name for this field." msgstr "Bitte geben Sie einen eindeutigen Namen für das Feld ein." -#: ../lib/modules/ppolicy.inc:81 +#: ../lib/modules/ppolicy.inc:80 msgid "Please enter a unique name for this password policy." msgstr "" "Bitte geben Sie einen eindeutigen Namen für diese Passwortrichtlinie ein." @@ -11119,11 +11121,11 @@ msgstr "" "Der LDAP-Attributname enthält ungültige Zeichen. Gültige Zeichen sind: a-z, " "0-9 und -_." -#: ../templates/selfService/adminMain.php:243 +#: ../templates/selfService/adminMain.php:249 msgid "Please enter a valid LDAP suffix." msgstr "Bitte geben Sie einen gültigen LDAP-Suffix ein." -#: ../templates/selfService/adminMain.php:247 +#: ../templates/selfService/adminMain.php:253 msgid "" "Please enter a valid LDAP user if you want to use it for all operations." msgstr "" @@ -11138,7 +11140,7 @@ msgstr "Bitte geben Sie einen gültigen Kontostatus ein." msgid "Please enter a valid admin email address." msgstr "Bitte geben Sie eine gültige Admin-EMail-Adresse ein." -#: ../templates/config/confmain.php:634 +#: ../templates/config/confmain.php:649 msgid "Please enter a valid bind user." msgstr "Bitte geben Sie einen gültigen Bind-Benutzer ein." @@ -11232,10 +11234,10 @@ msgstr "Bitte geben Sie einen gültigen Laufwerksbuchstaben ein." #: ../lib/modules/qmailGroup.inc:395 ../lib/modules/qmailGroup.inc:396 #: ../lib/modules/qmailGroup.inc:397 ../lib/modules/passwordSelfReset.inc:282 #: ../lib/modules/passwordSelfReset.inc:283 -#: ../lib/modules/passwordSelfReset.inc:1111 -#: ../lib/modules/passwordSelfReset.inc:1362 +#: ../lib/modules/passwordSelfReset.inc:1109 +#: ../lib/modules/passwordSelfReset.inc:1360 #: ../lib/modules/windowsGroup.inc:281 ../lib/modules/windowsGroup.inc:282 -#: ../lib/modules.inc:1255 +#: ../lib/modules.inc:1256 msgid "Please enter a valid email address!" msgstr "Bitte geben Sie eine gültige EMail Adresse ein!" @@ -11247,8 +11249,8 @@ msgstr "Bitte geben Sie eine gültige EMail Adresse ein!" msgid "Please enter a valid email alias." msgstr "Bitte geben Sie einen gültigen EMail-Alias ein." -#: ../lib/modules/passwordSelfReset.inc:1118 -#: ../lib/modules/passwordSelfReset.inc:1369 +#: ../lib/modules/passwordSelfReset.inc:1116 +#: ../lib/modules/passwordSelfReset.inc:1367 msgid "Please enter a valid employee number." msgstr "Bitte geben Sie eine gültige Personalnummer ein." @@ -11269,7 +11271,7 @@ msgstr "" "Bitte geben Sie einen gültigen Feldnamen ein. Es sind Buchstaben, Zahlen und " "Minus erlaubt." -#: ../lib/lists.inc:244 +#: ../lib/lists.inc:240 msgid "" "Please enter a valid filter. Only letters, numbers and \" _*$.@-\" are " "allowed." @@ -11313,8 +11315,8 @@ msgstr "Bitte geben Sie einen gültigen Jobsuffix an." msgid "Please enter a valid job title!" msgstr "Bitte geben Sie eine gültige Berufsbezeichnung ein!" -#: ../lib/modules/yubiKeyUser.inc:129 ../lib/modules/yubiKeyUser.inc:130 -#: ../lib/modules/yubiKeyUser.inc:433 +#: ../lib/modules/yubiKeyUser.inc:133 ../lib/modules/yubiKeyUser.inc:134 +#: ../lib/modules/yubiKeyUser.inc:430 msgid "Please enter a valid key id." msgstr "Bitte geben Sie eine gültige Schlüssel-ID ein." @@ -11364,7 +11366,7 @@ msgid "Please enter a valid number (e.g. \"1.5\")." msgstr "Bitte geben Sie eine gültige Zahl (z.B. \"1.5\") ein." #: ../lib/modules/pykotaPrinter.inc:212 ../lib/modules/pykotaPrinter.inc:213 -#: ../lib/modules/autoDelete.inc:104 ../lib/modules/posixGroup.inc:768 +#: ../lib/modules/autoDelete.inc:104 ../lib/modules/posixGroup.inc:717 #: ../lib/modules/customFields.inc:2316 ../lib/modules/customFields.inc:2325 #: ../lib/modules/customFields.inc:3888 ../lib/modules/customFields.inc:3892 msgid "Please enter a valid number." @@ -11379,13 +11381,13 @@ msgstr "Bitte geben Sie eine gültige Objektklasse ein." msgid "Please enter a valid option." msgstr "Bitte geben Sie eine gültige Option ein." -#: ../lib/modules/kopanoServer.inc:154 ../lib/modules/kopanoServer.inc:155 +#: ../lib/modules/kopanoServer.inc:153 ../lib/modules/kopanoServer.inc:154 #: ../lib/modules/zarafaServer.inc:154 ../lib/modules/zarafaServer.inc:155 msgid "Please enter a valid path." msgstr "Bitte geben Sie einen gültigen Pfad ein." -#: ../lib/modules/kopanoServer.inc:150 ../lib/modules/kopanoServer.inc:151 -#: ../lib/modules/kopanoServer.inc:152 ../lib/modules/kopanoServer.inc:153 +#: ../lib/modules/kopanoServer.inc:149 ../lib/modules/kopanoServer.inc:150 +#: ../lib/modules/kopanoServer.inc:151 ../lib/modules/kopanoServer.inc:152 #: ../lib/modules/asteriskAccount.inc:570 #: ../lib/modules/asteriskAccount.inc:571 ../lib/modules/zarafaServer.inc:150 #: ../lib/modules/zarafaServer.inc:151 ../lib/modules/zarafaServer.inc:152 @@ -11462,13 +11464,13 @@ msgstr "Bitte geben Sie einen gültigen Straßennamen ein!" msgid "Please enter a valid telephone number!" msgstr "Bitte geben Sie eine gültige Telefonnummer ein!" -#: ../lib/modules/passwordSelfReset.inc:1132 -#: ../lib/modules/passwordSelfReset.inc:1383 +#: ../lib/modules/passwordSelfReset.inc:1130 +#: ../lib/modules/passwordSelfReset.inc:1381 msgid "Please enter a valid user name or email address." msgstr "Bitte geben Sie einen gültigen Benutzernamen oder EMail-Adresse ein." -#: ../lib/modules/posixGroup.inc:902 ../lib/modules/posixAccount.inc:1141 -#: ../lib/modules/posixAccount.inc:1146 +#: ../lib/modules/posixGroup.inc:851 ../lib/modules/posixAccount.inc:1121 +#: ../lib/modules/posixAccount.inc:1126 #, php-format msgid "Please enter a value between %s and %s!" msgstr "Bitte geben Sie einen Wert zwischen %s und %s ein!" @@ -11481,15 +11483,15 @@ msgstr "Bitte geben Sie einen Wert für den Status \"ausgewählt\" an." msgid "Please enter a value for status \"unchecked\"." msgstr "Bitte geben Sie einen Wert für den Status \"nicht ausgewählt\" an." -#: ../templates/tools/multiEdit.php:162 +#: ../templates/tools/multiEdit.php:164 msgid "Please enter a value to add." msgstr "Bitte geben Sie einen Wert zum Hinzufügen an." -#: ../templates/tools/multiEdit.php:165 +#: ../templates/tools/multiEdit.php:167 msgid "Please enter a value to modify." msgstr "Bitte geben Sie einen Wert zum Editieren ein." -#: ../help/help.inc:364 +#: ../help/help.inc:370 msgid "Please enter an LDAP filter to specifiy the exported entries." msgstr "" "Bitte geben Sie einen LDAP-Filter zur Einschränkung der zu exportierenden " @@ -11526,8 +11528,8 @@ msgstr "Bitte geben Sie mindestens %s Werte ein." msgid "Please enter at least one range for pool \"%s\"." msgstr "Bitte geben Sie mindestens einen Bereich für den Pool \"%s\" an." -#: ../lib/modules/windowsUser.inc:2417 ../lib/modules/windowsUser.inc:2426 -#: ../lib/modules/windowsUser.inc:2447 ../lib/modules/windowsUser.inc:2458 +#: ../lib/modules/windowsUser.inc:2389 ../lib/modules/windowsUser.inc:2398 +#: ../lib/modules/windowsUser.inc:2419 ../lib/modules/windowsUser.inc:2430 #: ../lib/modules/customFields.inc:3568 msgid "Please enter either yes or no." msgstr "Bitte geben Sie entweder ja oder nein ein." @@ -11578,7 +11580,7 @@ msgstr "" msgid "Please enter the DN and password to use for all jobs." msgstr "Bitte geben Sie die DN und Passwort für die Jobs ein." -#: ../lib/modules/posixGroup.inc:519 ../lib/modules/posixAccount.inc:390 +#: ../lib/modules/posixGroup.inc:468 ../lib/modules/posixAccount.inc:395 msgid "" "Please enter the DN of the LDAP entry with object class \"msSFU30DomainInfo" "\"." @@ -11586,7 +11588,7 @@ msgstr "" "Bitte geben Sie die DN des LDAP-Eintrages mit der Objektklasse " "\"msSFU30DomainInfo\" ein." -#: ../lib/modules/posixGroup.inc:515 ../lib/modules/posixAccount.inc:386 +#: ../lib/modules/posixGroup.inc:464 ../lib/modules/posixAccount.inc:391 msgid "" "Please enter the DN of the LDAP entry with object class \"sambaUnixIdPool\"." msgstr "" @@ -11650,6 +11652,15 @@ msgid "" msgstr "" "Bitte geben Sie den LDAP-Suffix ein unter dem die Samba Domänen liegen." +#: ../help/help.inc:333 +msgid "" +"Please enter the WebAuthn domain. This is the public domain of the webserver " +"(e.g. \"example.com\"). Do not include protocol or port." +msgstr "" +"Bitte geben Sie die WebAuthn Domäne ein. Dies ist der öffentliche " +"Domänenname des Webservers (z.B. \"example.com\"). Geben Sie kein Protokoll " +"oder Port an." + #: ../lib/modules/asteriskExtension.inc:189 #: ../lib/modules/asteriskVoicemail.inc:213 msgid "Please enter the account context." @@ -11721,7 +11732,7 @@ msgstr "" "Passwort. Es befindet sich in ihrer .conf-Datei. Wenn Sie sich zum ersten " "Mal anmelden geben Sie \"lam\" ein." -#: ../lib/modules/oracleService.inc:66 +#: ../lib/modules/oracleService.inc:65 msgid "" "Please enter the connection string (e.g. " "\"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=server)(PORT=1521))" @@ -11747,11 +11758,11 @@ msgstr "Bitte geben Sie den Erweiterungsnamen an." msgid "Please enter the group name." msgstr "Bitte geben Sie den Gruppennamen an." -#: ../lib/modules/asteriskAccount.inc:560 ../lib/modules/windowsHost.inc:75 +#: ../lib/modules/asteriskAccount.inc:560 ../lib/modules/windowsHost.inc:74 msgid "Please enter the host name." msgstr "Bitte geben Sie den Hostnamen an." -#: ../lib/modules/customScripts.inc:60 +#: ../lib/modules/customScripts.inc:59 msgid "" "Please enter the list of scripts which should be run. Each line has the " "following format: [account type] [action] [script and arguments]" @@ -11759,7 +11770,7 @@ msgstr "" "Bitte geben Sie die Liste der zu startenden Skripte an. Jede Zeile hat " "folgendes Format: [Accounttyp] [Aktion] [Skript und Argumente]" -#: ../lib/modules/customScripts.inc:74 +#: ../lib/modules/customScripts.inc:73 msgid "" "Please enter the list of scripts which should be run. Each line has the " "following format: [action] [script and arguments]" @@ -11767,7 +11778,7 @@ msgstr "" "Bitte geben Sie die Liste der zu startenden Skripte an. Jede Zeile hat " "folgendes Format: [Aktion] [Skript und Argumente]" -#: ../lib/modules/posixGroup.inc:540 ../lib/modules/posixAccount.inc:394 +#: ../lib/modules/posixGroup.inc:489 ../lib/modules/posixAccount.inc:399 msgid "Please enter the magic number you configured on server side." msgstr "" "Bitte geben Sie die magische Zahl an, die auf Serverseite konfiguriert ist." @@ -11819,7 +11830,7 @@ msgstr "" "Bitte geben Sie den neuen Namen des Profils an. Profilnamen dürfen " "Buchstaben, Zahlen und -/_ enthalten." -#: ../help/help.inc:391 +#: ../help/help.inc:397 msgid "" "Please enter the number of days before password expiration to send out the " "email." @@ -11848,12 +11859,12 @@ msgstr "" "Bitte geben Sie das Passwort des administrativen Benutzers an. Bitte " "beachten Sie, dass es als Klartext im SelfService-Profil gespeichert wird." -#: ../lib/modules/asteriskAccount.inc:115 ../lib/modules/posixAccount.inc:423 +#: ../lib/modules/asteriskAccount.inc:115 ../lib/modules/posixAccount.inc:428 #: ../lib/modules/windowsUser.inc:244 ../lib/modules/inetOrgPerson.inc:783 msgid "Please enter the password which you want to set for this account." msgstr "Bitte geben Sie das Passwort für diesen Account ein." -#: ../help/help.inc:255 +#: ../help/help.inc:257 msgid "" "Please enter the password which you want to set for this account. You may " "also generate a random password (12 characters) which will be displayed on " @@ -11863,16 +11874,16 @@ msgstr "" "Zufallspasswort (12 Zeichen) erzeugen, das dann auf dem Bildschirm angezeigt " "wird." -#: ../lib/modules/posixAccount.inc:353 ../lib/modules/posixAccount.inc:357 +#: ../lib/modules/posixAccount.inc:358 ../lib/modules/posixAccount.inc:362 msgid "Please enter the path to the user's home directory." msgstr "Bitte geben Sie den Pfad zum Heimatverzeichnis des Benutzers an." -#: ../lib/modules/kopanoServer.inc:69 ../lib/modules/zarafaServer.inc:69 +#: ../lib/modules/kopanoServer.inc:68 ../lib/modules/zarafaServer.inc:69 msgid "Please enter the port number for (unencrypted) HTTP connections." msgstr "" "Bitte geben Sie die Portnummer für (unverschlüsselte) HTTP-Verbindungen ein." -#: ../lib/modules/kopanoServer.inc:73 ../lib/modules/zarafaServer.inc:73 +#: ../lib/modules/kopanoServer.inc:72 ../lib/modules/zarafaServer.inc:73 msgid "Please enter the port number for encrypted connections." msgstr "Bitte geben Sie die Portnummer für verschlüsselte Verbindungen ein." @@ -11934,7 +11945,7 @@ msgstr "Bitte geben Sie das selbe Passwort in beide Felder ein." msgid "Please enter the security question for the password self reset." msgstr "Bitte geben Sie die Sicherheitsfrage für die Passwortrücksetzung ein." -#: ../help/help.inc:313 +#: ../help/help.inc:317 msgid "Please enter the site and secret key you got from Google reCAPTCHA." msgstr "" "Bitte geben Sie den Seitenschlüssel und den geheimen Schlüssel ein, den Sie " @@ -11977,11 +11988,7 @@ msgstr "" "Bitte geben Sie die Puppet Variablen für diesen Knoten (z.B. " "config_exim=true) ein." -#: ../lib/modules/yubiKeyUser.inc:89 -msgid "Please enter your YubiKey ids." -msgstr "Bitte geben Sie Ihre YubiKey IDs ein." - -#: ../help/help.inc:319 +#: ../help/help.inc:323 msgid "Please enter your client id for the verification API." msgstr "Bitte geben Sie die Client-ID für die Verifzierungs-API an." @@ -12005,7 +12012,7 @@ msgstr "" msgid "Please enter your public SSH key." msgstr "Bitte geben Sie Ihren SSH-Schlüssel ein." -#: ../help/help.inc:321 +#: ../help/help.inc:325 msgid "Please enter your secret key for the verification API." msgstr "Bitte geben Sie den geheimen Schlüssel für die Verifzierungs-API an." @@ -12036,7 +12043,7 @@ msgstr "" "Zellen der ersten Zeile müssen mit den Spaltenbezeichnern gefüllt werden. " "Alle nachfolgenden Zeilen repräsentieren jeweils einen Account." -#: ../lib/config.inc:2661 +#: ../lib/config.inc:2920 msgid "Please provide a file in DER or PEM format." msgstr "Bitte geben Sie eine Datei im DER oder PEM-Format an." @@ -12056,7 +12063,7 @@ msgstr "" msgid "Please select the field type (e.g. text field)." msgstr "Bitte wählen Sie den Feldtyp (z.B. Textfeld)." -#: ../help/help.inc:269 +#: ../help/help.inc:271 msgid "" "Please select the font for the PDF file. Dejavu will work on all systems but " "does not support e.g. Chinese and Japanese. The other fonts require that an " @@ -12067,7 +12074,7 @@ msgstr "" "anderen Schriftarten erfordern, dass die entsprechenden Schriften auf dem " "Zielsystem installiert sind." -#: ../help/help.inc:352 +#: ../help/help.inc:358 msgid "Please select the suffix where changes should be done." msgstr "Bitte wählen Sie den Suffix wo Änderungen stattfinden sollen." @@ -12128,7 +12135,7 @@ msgstr "" msgid "Please setup your licence data." msgstr "Bitte geben Sie Ihre Lizenzdaten ein." -#: ../templates/tools/multiEdit.php:238 +#: ../templates/tools/multiEdit.php:240 msgid "Please specify at least one operation." msgstr "Bitte geben Sie mindestens eine Operation ein." @@ -12156,7 +12163,7 @@ msgstr "" "Bitte geben Sie die möglichen Werte des Feldes ein. Sie können jedem Wert " "eine Beschreibung zuordnen, die dem Benutzer statt des Wertes angezeigt wird." -#: ../help/help.inc:356 +#: ../help/help.inc:362 msgid "" "Please specify which attributes should be changed. The modify operation will " "also add an value if the attribute does not yet exist. To delete all values " @@ -12192,8 +12199,8 @@ msgstr "Pools" #: ../lib/modules/asteriskAccount.inc:281 #: ../lib/modules/asteriskAccount.inc:434 #: ../lib/modules/asteriskAccount.inc:684 -#: ../lib/modules/asteriskAccount.inc:998 -#: ../lib/modules/asteriskAccount.inc:1242 ../lib/modules/bindDLZ.inc:204 +#: ../lib/modules/asteriskAccount.inc:999 +#: ../lib/modules/asteriskAccount.inc:1243 ../lib/modules/bindDLZ.inc:204 #: ../lib/modules/bindDLZ.inc:208 ../lib/modules/bindDLZ.inc:469 #: ../lib/modules/bindDLZ.inc:602 ../lib/modules/bindDLZ.inc:1693 #: ../lib/modules/bindDLZ.inc:1966 @@ -12209,9 +12216,9 @@ msgstr "Portnummer." msgid "Position" msgstr "Position" -#: ../templates/upload/masscreate.php:325 ../lib/modules/customScripts.inc:112 -#: ../lib/modules/customScripts.inc:114 ../lib/modules/customScripts.inc:116 -#: ../lib/modules/sambaGroupMapping.inc:578 +#: ../templates/upload/masscreate.php:325 ../lib/modules/customScripts.inc:111 +#: ../lib/modules/customScripts.inc:113 ../lib/modules/customScripts.inc:115 +#: ../lib/modules/sambaGroupMapping.inc:575 msgid "Possible values" msgstr "Mögliche Werte" @@ -12228,23 +12235,23 @@ msgstr "" #: ../lib/modules/windowsUser.inc:181 ../lib/modules/windowsUser.inc:466 #: ../lib/modules/windowsUser.inc:851 ../lib/modules/windowsUser.inc:949 -#: ../lib/modules/windowsUser.inc:1134 ../lib/modules/windowsUser.inc:2725 -#: ../lib/modules/windowsUser.inc:2936 ../lib/modules/inetOrgPerson.inc:161 +#: ../lib/modules/windowsUser.inc:1133 ../lib/modules/windowsUser.inc:2697 +#: ../lib/modules/windowsUser.inc:2908 ../lib/modules/inetOrgPerson.inc:161 #: ../lib/modules/inetOrgPerson.inc:339 ../lib/modules/inetOrgPerson.inc:486 #: ../lib/modules/inetOrgPerson.inc:606 ../lib/modules/inetOrgPerson.inc:610 #: ../lib/modules/inetOrgPerson.inc:1254 ../lib/modules/inetOrgPerson.inc:1257 -#: ../lib/modules/inetOrgPerson.inc:1951 ../lib/modules/inetOrgPerson.inc:2009 -#: ../lib/modules/inetOrgPerson.inc:2691 ../lib/modules/inetOrgPerson.inc:3860 -#: ../lib/modules/inetOrgPerson.inc:3904 +#: ../lib/modules/inetOrgPerson.inc:1952 ../lib/modules/inetOrgPerson.inc:2010 +#: ../lib/modules/inetOrgPerson.inc:2692 ../lib/modules/inetOrgPerson.inc:3861 +#: ../lib/modules/inetOrgPerson.inc:3905 msgid "Post office box" msgstr "Postfach" #: ../lib/modules/inetOrgPerson.inc:88 ../lib/modules/inetOrgPerson.inc:160 #: ../lib/modules/inetOrgPerson.inc:323 ../lib/modules/inetOrgPerson.inc:498 #: ../lib/modules/inetOrgPerson.inc:622 ../lib/modules/inetOrgPerson.inc:1298 -#: ../lib/modules/inetOrgPerson.inc:1954 ../lib/modules/inetOrgPerson.inc:2021 -#: ../lib/modules/inetOrgPerson.inc:2658 ../lib/modules/inetOrgPerson.inc:3864 -#: ../lib/modules/inetOrgPerson.inc:3906 +#: ../lib/modules/inetOrgPerson.inc:1955 ../lib/modules/inetOrgPerson.inc:2022 +#: ../lib/modules/inetOrgPerson.inc:2659 ../lib/modules/inetOrgPerson.inc:3865 +#: ../lib/modules/inetOrgPerson.inc:3907 msgid "Postal address" msgstr "Anschrift" @@ -12254,15 +12261,15 @@ msgstr "Bitte geben Sie hier die Anschrift ein." #: ../lib/modules/windowsUser.inc:177 ../lib/modules/windowsUser.inc:472 #: ../lib/modules/windowsUser.inc:850 ../lib/modules/windowsUser.inc:950 -#: ../lib/modules/windowsUser.inc:1012 ../lib/modules/windowsUser.inc:1135 -#: ../lib/modules/windowsUser.inc:2724 ../lib/modules/windowsUser.inc:2937 +#: ../lib/modules/windowsUser.inc:1012 ../lib/modules/windowsUser.inc:1134 +#: ../lib/modules/windowsUser.inc:2696 ../lib/modules/windowsUser.inc:2909 #: ../lib/modules/inetOrgPerson.inc:92 ../lib/modules/inetOrgPerson.inc:161 #: ../lib/modules/inetOrgPerson.inc:315 ../lib/modules/inetOrgPerson.inc:489 #: ../lib/modules/inetOrgPerson.inc:614 ../lib/modules/inetOrgPerson.inc:618 #: ../lib/modules/inetOrgPerson.inc:1263 ../lib/modules/inetOrgPerson.inc:1266 -#: ../lib/modules/inetOrgPerson.inc:1952 ../lib/modules/inetOrgPerson.inc:2012 -#: ../lib/modules/inetOrgPerson.inc:2680 ../lib/modules/inetOrgPerson.inc:3861 -#: ../lib/modules/inetOrgPerson.inc:3904 +#: ../lib/modules/inetOrgPerson.inc:1953 ../lib/modules/inetOrgPerson.inc:2013 +#: ../lib/modules/inetOrgPerson.inc:2681 ../lib/modules/inetOrgPerson.inc:3862 +#: ../lib/modules/inetOrgPerson.inc:3905 msgid "Postal code" msgstr "Postleitzahl" @@ -12270,7 +12277,7 @@ msgstr "Postleitzahl" msgid "Predefined classes" msgstr "Vordefinierte Klassen" -#: ../lib/modules/puppetClient.inc:123 ../lib/modules/puppetClient.inc:524 +#: ../lib/modules/puppetClient.inc:123 ../lib/modules/puppetClient.inc:520 msgid "Predefined environments" msgstr "Vordefinierte Umgebungen" @@ -12297,21 +12304,21 @@ msgstr "Präfix für Postfächer" msgid "President" msgstr "Präsident" -#: ../lib/modules/pykotaUser.inc:615 ../lib/modules/pykotaUser.inc:1011 +#: ../lib/modules/pykotaUser.inc:607 ../lib/modules/pykotaUser.inc:993 msgid "Price" msgstr "Preis" #: ../lib/types/pykotaPrinterType.inc:96 ../lib/modules/pykotaPrinter.inc:110 #: ../lib/modules/pykotaPrinter.inc:173 ../lib/modules/pykotaPrinter.inc:196 #: ../lib/modules/pykotaPrinter.inc:214 ../lib/modules/pykotaPrinter.inc:234 -#: ../lib/modules/pykotaPrinter.inc:582 +#: ../lib/modules/pykotaPrinter.inc:574 msgid "Price per job" msgstr "Preis je Job" #: ../lib/types/pykotaPrinterType.inc:95 ../lib/modules/pykotaPrinter.inc:114 #: ../lib/modules/pykotaPrinter.inc:179 ../lib/modules/pykotaPrinter.inc:197 #: ../lib/modules/pykotaPrinter.inc:216 ../lib/modules/pykotaPrinter.inc:236 -#: ../lib/modules/pykotaPrinter.inc:583 +#: ../lib/modules/pykotaPrinter.inc:575 msgid "Price per page" msgstr "Preis je Seite" @@ -12321,11 +12328,11 @@ msgstr "Preis je Seite" msgid "Primary affiliation" msgstr "Primäre Zugehörigkeit" -#: ../lib/modules/posixAccount.inc:219 ../lib/modules/posixAccount.inc:281 -#: ../lib/modules/posixAccount.inc:298 ../lib/modules/posixAccount.inc:344 -#: ../lib/modules/posixAccount.inc:418 ../lib/modules/posixAccount.inc:456 -#: ../lib/modules/posixAccount.inc:1651 ../lib/modules/posixAccount.inc:2009 -#: ../lib/modules/posixAccount.inc:2048 ../lib/modules/posixAccount.inc:2144 +#: ../lib/modules/posixAccount.inc:224 ../lib/modules/posixAccount.inc:286 +#: ../lib/modules/posixAccount.inc:303 ../lib/modules/posixAccount.inc:349 +#: ../lib/modules/posixAccount.inc:423 ../lib/modules/posixAccount.inc:461 +#: ../lib/modules/posixAccount.inc:1634 ../lib/modules/posixAccount.inc:1975 +#: ../lib/modules/posixAccount.inc:2011 ../lib/modules/posixAccount.inc:2102 msgid "Primary group" msgstr "Primäre Gruppe" @@ -12355,7 +12362,7 @@ msgstr "Benutzername" msgid "Principal name is invalid!" msgstr "Der Benutzername ist ungültig!" -#: ../lib/modules/pykotaUser.inc:613 ../lib/modules/pykotaUser.inc:1010 +#: ../lib/modules/pykotaUser.inc:607 ../lib/modules/pykotaUser.inc:992 msgid "Printer" msgstr "Drucker" @@ -12368,16 +12375,16 @@ msgstr "Anzahl Drucker: %s" msgid "Printer description." msgstr "Druckerbeschreibung" -#: ../lib/modules/pykotaPrinter.inc:199 ../lib/modules/pykotaPrinter.inc:257 -#: ../lib/modules/pykotaPrinter.inc:616 +#: ../lib/modules/pykotaPrinter.inc:199 ../lib/modules/pykotaPrinter.inc:256 +#: ../lib/modules/pykotaPrinter.inc:608 msgid "Printer groups" msgstr "Druckergruppen" #: ../lib/types/pykotaPrinterType.inc:94 ../lib/modules/pykotaPrinter.inc:94 #: ../lib/modules/pykotaPrinter.inc:145 ../lib/modules/pykotaPrinter.inc:192 #: ../lib/modules/pykotaPrinter.inc:208 ../lib/modules/pykotaPrinter.inc:210 -#: ../lib/modules/pykotaPrinter.inc:230 ../lib/modules/pykotaPrinter.inc:579 -#: ../lib/modules/pykotaBillingCode.inc:101 +#: ../lib/modules/pykotaPrinter.inc:230 ../lib/modules/pykotaPrinter.inc:571 +#: ../lib/modules/pykotaBillingCode.inc:100 msgid "Printer name" msgstr "Druckername" @@ -12409,7 +12416,7 @@ msgstr "Drucker" #: ../lib/modules/bindDLZ.inc:459 ../lib/modules/bindDLZ.inc:598 #: ../lib/modules/bindDLZ.inc:1683 ../lib/modules/bindDLZ.inc:1964 #: ../lib/modules/asteriskExtension.inc:107 -#: ../lib/modules/asteriskExtension.inc:798 +#: ../lib/modules/asteriskExtension.inc:796 msgid "Priority" msgstr "Priorität" @@ -12472,12 +12479,12 @@ msgstr "Profilname" #: ../templates/config/profmanage.php:143 #: ../templates/config/profmanage.php:154 #: ../templates/selfService/profManage.php:65 -#: ../templates/selfService/profManage.php:84 ../lib/config.inc:190 +#: ../templates/selfService/profManage.php:84 ../lib/config.inc:203 msgid "Profile name is invalid!" msgstr "Profilname ist ungültig!" #: ../templates/config/profmanage.php:204 -#: ../templates/config/profmanage.php:259 ../templates/config/confmain.php:504 +#: ../templates/config/profmanage.php:259 ../templates/config/confmain.php:519 msgid "Profile password" msgstr "Profilpasswort" @@ -12491,16 +12498,16 @@ msgstr "Die Passwörter stimmen nicht überein!" #: ../lib/modules/sambaSamAccount.inc:310 #: ../lib/modules/sambaSamAccount.inc:359 #: ../lib/modules/sambaSamAccount.inc:510 -#: ../lib/modules/sambaSamAccount.inc:1163 -#: ../lib/modules/sambaSamAccount.inc:1469 -#: ../lib/modules/sambaSamAccount.inc:1538 -#: ../lib/modules/sambaSamAccount.inc:1728 -#: ../lib/modules/sambaSamAccount.inc:1905 -#: ../lib/modules/sambaSamAccount.inc:1926 -#: ../lib/modules/sambaSamAccount.inc:1942 ../lib/modules/windowsUser.inc:226 +#: ../lib/modules/sambaSamAccount.inc:1205 +#: ../lib/modules/sambaSamAccount.inc:1530 +#: ../lib/modules/sambaSamAccount.inc:1596 +#: ../lib/modules/sambaSamAccount.inc:1775 +#: ../lib/modules/sambaSamAccount.inc:1952 +#: ../lib/modules/sambaSamAccount.inc:1973 +#: ../lib/modules/sambaSamAccount.inc:1989 ../lib/modules/windowsUser.inc:226 #: ../lib/modules/windowsUser.inc:570 ../lib/modules/windowsUser.inc:766 #: ../lib/modules/windowsUser.inc:861 ../lib/modules/windowsUser.inc:1022 -#: ../lib/modules/windowsUser.inc:1249 ../lib/modules/windowsUser.inc:2772 +#: ../lib/modules/windowsUser.inc:1256 ../lib/modules/windowsUser.inc:2744 msgid "Profile path" msgstr "Profilpfad" @@ -12521,22 +12528,22 @@ msgstr "Profile" msgid "Program to execute for all incoming mails." msgstr "Programm, das für alle eingehenden Nachrichten ausgeführt wird." -#: ../templates/tools/multiEdit.php:256 +#: ../templates/tools/multiEdit.php:258 msgid "Progress" msgstr "Fortschritt" -#: ../help/help.inc:315 +#: ../help/help.inc:319 msgid "Protect the self service login with a captcha." msgstr "Damit schützen Sie den SelfService mit einem Captcha." -#: ../templates/config/confmain.php:466 -#: ../templates/selfService/adminMain.php:486 +#: ../templates/config/confmain.php:473 +#: ../templates/selfService/adminMain.php:493 msgid "Provider" msgstr "Dienst" -#: ../lib/modules/kopanoServer.inc:84 ../lib/modules/kopanoServer.inc:128 -#: ../lib/modules/kopanoServer.inc:141 ../lib/modules/kopanoServer.inc:189 -#: ../lib/modules/kopanoServer.inc:330 ../lib/modules/kopanoServer.inc:398 +#: ../lib/modules/kopanoServer.inc:83 ../lib/modules/kopanoServer.inc:127 +#: ../lib/modules/kopanoServer.inc:140 ../lib/modules/kopanoServer.inc:174 +#: ../lib/modules/kopanoServer.inc:310 ../lib/modules/kopanoServer.inc:378 #: ../lib/modules/zarafaServer.inc:84 ../lib/modules/zarafaServer.inc:128 #: ../lib/modules/zarafaServer.inc:141 ../lib/modules/zarafaServer.inc:189 #: ../lib/modules/zarafaServer.inc:330 ../lib/modules/zarafaServer.inc:399 @@ -12545,9 +12552,9 @@ msgstr "Proxy-URL" #: ../lib/types/user.inc:111 ../lib/modules/windowsUser.inc:386 #: ../lib/modules/windowsUser.inc:390 ../lib/modules/windowsUser.inc:601 -#: ../lib/modules/windowsUser.inc:871 ../lib/modules/windowsUser.inc:1144 -#: ../lib/modules/windowsUser.inc:2736 ../lib/modules/windowsUser.inc:2957 -#: ../lib/modules/windowsUser.inc:3629 +#: ../lib/modules/windowsUser.inc:871 ../lib/modules/windowsUser.inc:1143 +#: ../lib/modules/windowsUser.inc:2708 ../lib/modules/windowsUser.inc:2929 +#: ../lib/modules/windowsUser.inc:3601 msgid "Proxy-Addresses" msgstr "Proxy-Adressen" @@ -12555,9 +12562,9 @@ msgstr "Proxy-Adressen" msgid "Proxy-Addresses (read-only)" msgstr "Proxy-Adressen (nur lesen)" -#: ../lib/modules/kopanoServer.inc:80 ../lib/modules/kopanoServer.inc:118 -#: ../lib/modules/kopanoServer.inc:138 ../lib/modules/kopanoServer.inc:202 -#: ../lib/modules/kopanoServer.inc:335 ../lib/modules/zarafaServer.inc:80 +#: ../lib/modules/kopanoServer.inc:79 ../lib/modules/kopanoServer.inc:117 +#: ../lib/modules/kopanoServer.inc:137 ../lib/modules/kopanoServer.inc:183 +#: ../lib/modules/kopanoServer.inc:315 ../lib/modules/zarafaServer.inc:80 #: ../lib/modules/zarafaServer.inc:118 ../lib/modules/zarafaServer.inc:138 #: ../lib/modules/zarafaServer.inc:202 ../lib/modules/zarafaServer.inc:335 msgid "Public store" @@ -12568,7 +12575,7 @@ msgid "Puppet" msgstr "Puppet" #: ../lib/modules/pykotaPrinter.inc:78 ../lib/modules/pykotaGroup.inc:93 -#: ../lib/modules/pykotaUser.inc:96 ../lib/modules/pykotaBillingCode.inc:61 +#: ../lib/modules/pykotaUser.inc:96 ../lib/modules/pykotaBillingCode.inc:60 msgid "PyKota" msgstr "PyKota" @@ -12578,7 +12585,7 @@ msgstr "PyKota Rechnungscodes" #: ../lib/modules/pykotaGroup.inc:115 ../lib/modules/pykotaGroup.inc:142 #: ../lib/modules/pykotaGroup.inc:158 ../lib/modules/pykotaGroup.inc:172 -#: ../lib/modules/pykotaGroup.inc:174 ../lib/modules/pykotaGroup.inc:489 +#: ../lib/modules/pykotaGroup.inc:174 ../lib/modules/pykotaGroup.inc:488 msgid "PyKota group name" msgstr "PyKota Gruppenname" @@ -12588,7 +12595,7 @@ msgstr "PyKota Drucker" #: ../lib/modules/pykotaUser.inc:127 ../lib/modules/pykotaUser.inc:193 #: ../lib/modules/pykotaUser.inc:237 ../lib/modules/pykotaUser.inc:278 -#: ../lib/modules/pykotaUser.inc:280 ../lib/modules/pykotaUser.inc:907 +#: ../lib/modules/pykotaUser.inc:280 ../lib/modules/pykotaUser.inc:889 msgid "PyKota user name" msgstr "PyKota Benutzername" @@ -12596,12 +12603,12 @@ msgstr "PyKota Benutzername" msgid "Pykota group name" msgstr "PyKota Gruppenname" -#: ../lib/modules/pykotaUser.inc:323 +#: ../lib/modules/pykotaUser.inc:322 msgid "Pykota user name" msgstr "PyKota Benutzername" -#: ../lib/modules/qmailUser.inc:87 ../lib/modules/qmailUser.inc:1251 -#: ../lib/modules/qmailUser.inc:1321 ../lib/modules/qmailGroup.inc:56 +#: ../lib/modules/qmailUser.inc:87 ../lib/modules/qmailUser.inc:1091 +#: ../lib/modules/qmailUser.inc:1161 ../lib/modules/qmailGroup.inc:56 msgid "Qmail" msgstr "Qmail" @@ -12609,8 +12616,8 @@ msgstr "Qmail" #: ../lib/modules/asteriskAccount.inc:282 #: ../lib/modules/asteriskAccount.inc:441 #: ../lib/modules/asteriskAccount.inc:689 -#: ../lib/modules/asteriskAccount.inc:999 -#: ../lib/modules/asteriskAccount.inc:1243 +#: ../lib/modules/asteriskAccount.inc:1000 +#: ../lib/modules/asteriskAccount.inc:1244 msgid "Qualify" msgstr "Verbindungsprüfung" @@ -12640,17 +12647,17 @@ msgstr "Abfrageattribut" #: ../lib/modules/passwordSelfReset.inc:322 #: ../lib/modules/passwordSelfReset.inc:326 #: ../lib/modules/passwordSelfReset.inc:329 +#: ../lib/modules/passwordSelfReset.inc:491 +#: ../lib/modules/passwordSelfReset.inc:492 #: ../lib/modules/passwordSelfReset.inc:493 -#: ../lib/modules/passwordSelfReset.inc:494 -#: ../lib/modules/passwordSelfReset.inc:495 +#: ../lib/modules/passwordSelfReset.inc:766 +#: ../lib/modules/passwordSelfReset.inc:767 #: ../lib/modules/passwordSelfReset.inc:768 -#: ../lib/modules/passwordSelfReset.inc:769 -#: ../lib/modules/passwordSelfReset.inc:770 -#: ../lib/modules/passwordSelfReset.inc:1279 +#: ../lib/modules/passwordSelfReset.inc:1277 msgid "Question" msgstr "Frage" -#: ../lib/modules/qmailUser.inc:633 ../lib/modules/quota.inc:89 +#: ../lib/modules/qmailUser.inc:523 ../lib/modules/quota.inc:89 #: ../lib/modules/quota.inc:102 ../lib/modules/quota.inc:157 #: ../lib/modules/pykotaGroup.inc:61 ../lib/modules/pykotaGroup.inc:152 #: ../lib/modules/pykotaGroup.inc:153 ../lib/modules/zarafaUser.inc:511 @@ -12658,12 +12665,12 @@ msgstr "Frage" #: ../lib/modules/pykotaUser.inc:213 ../lib/modules/systemQuotas.inc:60 #: ../lib/modules/systemQuotas.inc:70 ../lib/modules/systemQuotas.inc:77 #: ../lib/modules/systemQuotas.inc:83 ../lib/modules/systemQuotas.inc:90 -#: ../lib/modules/kopanoUser.inc:511 ../lib/modules/imapAccess.inc:113 +#: ../lib/modules/kopanoUser.inc:495 ../lib/modules/imapAccess.inc:113 #: ../lib/modules/imapAccess.inc:140 msgid "Quota" msgstr "Quota" -#: ../lib/modules/quota.inc:813 +#: ../lib/modules/quota.inc:781 #, php-format msgid "Quota for %s on %s" msgstr "Quota für %s auf %s" @@ -12674,8 +12681,8 @@ msgstr "Quota für %s auf %s" #: ../lib/modules/zarafaUser.inc:1356 ../lib/modules/zarafaUser.inc:1823 #: ../lib/modules/kopanoUser.inc:118 ../lib/modules/kopanoUser.inc:252 #: ../lib/modules/kopanoUser.inc:375 ../lib/modules/kopanoUser.inc:435 -#: ../lib/modules/kopanoUser.inc:546 ../lib/modules/kopanoUser.inc:1272 -#: ../lib/modules/kopanoUser.inc:1356 ../lib/modules/kopanoUser.inc:1834 +#: ../lib/modules/kopanoUser.inc:516 ../lib/modules/kopanoUser.inc:1191 +#: ../lib/modules/kopanoUser.inc:1275 ../lib/modules/kopanoUser.inc:1753 msgid "Quota hard limit" msgstr "Hartes Quotalimit" @@ -12693,15 +12700,15 @@ msgstr "Quotalimit (kB)" #: ../lib/modules/zarafaUser.inc:1283 ../lib/modules/zarafaUser.inc:1347 #: ../lib/modules/zarafaUser.inc:1820 ../lib/modules/kopanoUser.inc:106 #: ../lib/modules/kopanoUser.inc:226 ../lib/modules/kopanoUser.inc:366 -#: ../lib/modules/kopanoUser.inc:518 ../lib/modules/kopanoUser.inc:1283 -#: ../lib/modules/kopanoUser.inc:1347 ../lib/modules/kopanoUser.inc:1831 +#: ../lib/modules/kopanoUser.inc:502 ../lib/modules/kopanoUser.inc:1202 +#: ../lib/modules/kopanoUser.inc:1266 ../lib/modules/kopanoUser.inc:1750 msgid "Quota override" msgstr "Quotas überschreiben" #: ../lib/modules/qmailUser.inc:100 ../lib/modules/qmailUser.inc:185 #: ../lib/modules/qmailUser.inc:265 ../lib/modules/qmailUser.inc:363 -#: ../lib/modules/qmailUser.inc:640 ../lib/modules/qmailUser.inc:1095 -#: ../lib/modules/qmailUser.inc:1215 +#: ../lib/modules/qmailUser.inc:526 ../lib/modules/qmailUser.inc:935 +#: ../lib/modules/qmailUser.inc:1055 msgid "Quota size" msgstr "Größenbeschränkung" @@ -12711,8 +12718,8 @@ msgstr "Größenbeschränkung" #: ../lib/modules/zarafaUser.inc:1353 ../lib/modules/zarafaUser.inc:1822 #: ../lib/modules/kopanoUser.inc:114 ../lib/modules/kopanoUser.inc:244 #: ../lib/modules/kopanoUser.inc:372 ../lib/modules/kopanoUser.inc:433 -#: ../lib/modules/kopanoUser.inc:536 ../lib/modules/kopanoUser.inc:1271 -#: ../lib/modules/kopanoUser.inc:1353 ../lib/modules/kopanoUser.inc:1833 +#: ../lib/modules/kopanoUser.inc:511 ../lib/modules/kopanoUser.inc:1190 +#: ../lib/modules/kopanoUser.inc:1272 ../lib/modules/kopanoUser.inc:1752 msgid "Quota soft limit" msgstr "Weiches Quotalimit" @@ -12722,8 +12729,8 @@ msgstr "Weiches Quotalimit" #: ../lib/modules/zarafaUser.inc:1350 ../lib/modules/zarafaUser.inc:1821 #: ../lib/modules/kopanoUser.inc:110 ../lib/modules/kopanoUser.inc:236 #: ../lib/modules/kopanoUser.inc:369 ../lib/modules/kopanoUser.inc:431 -#: ../lib/modules/kopanoUser.inc:526 ../lib/modules/kopanoUser.inc:1270 -#: ../lib/modules/kopanoUser.inc:1350 ../lib/modules/kopanoUser.inc:1832 +#: ../lib/modules/kopanoUser.inc:506 ../lib/modules/kopanoUser.inc:1189 +#: ../lib/modules/kopanoUser.inc:1269 ../lib/modules/kopanoUser.inc:1751 msgid "Quota warning limit" msgstr "Quotawarnlimit" @@ -12731,7 +12738,7 @@ msgstr "Quotawarnlimit" #: ../templates/profedit/profilepage.php:216 ../lib/modules/dynamicList.inc:58 #: ../lib/modules/dynamicList.inc:245 ../lib/modules/selfRegistration.inc:97 #: ../lib/modules/selfRegistration.inc:200 ../lib/modules.inc:1075 -#: ../help/help.inc:236 +#: ../help/help.inc:236 ../help/help.inc:250 msgid "RDN identifier" msgstr "RDN-Bezeichner" @@ -12739,13 +12746,13 @@ msgstr "RDN-Bezeichner" msgid "RID (Windows UID)" msgstr "RID (Windows UID)" -#: ../lib/modules/sambaDomain.inc:97 ../lib/modules/sambaDomain.inc:158 -#: ../lib/modules/sambaDomain.inc:189 ../lib/modules/sambaDomain.inc:383 -#: ../lib/modules/sambaDomain.inc:389 ../lib/modules/sambaDomain.inc:598 +#: ../lib/modules/sambaDomain.inc:96 ../lib/modules/sambaDomain.inc:157 +#: ../lib/modules/sambaDomain.inc:188 ../lib/modules/sambaDomain.inc:378 +#: ../lib/modules/sambaDomain.inc:384 ../lib/modules/sambaDomain.inc:595 msgid "RID base" msgstr "RID-Basis" -#: ../lib/modules/sambaDomain.inc:352 +#: ../lib/modules/sambaDomain.inc:345 msgid "RID settings" msgstr "RID-Einstellungen" @@ -12753,8 +12760,8 @@ msgstr "RID-Einstellungen" #: ../lib/modules/asteriskAccount.inc:285 #: ../lib/modules/asteriskAccount.inc:463 #: ../lib/modules/asteriskAccount.inc:702 -#: ../lib/modules/asteriskAccount.inc:1002 -#: ../lib/modules/asteriskAccount.inc:1246 +#: ../lib/modules/asteriskAccount.inc:1003 +#: ../lib/modules/asteriskAccount.inc:1247 msgid "RTP hold timeout" msgstr "RTP-Halte-Timeout" @@ -12762,8 +12769,8 @@ msgstr "RTP-Halte-Timeout" #: ../lib/modules/asteriskAccount.inc:284 #: ../lib/modules/asteriskAccount.inc:456 #: ../lib/modules/asteriskAccount.inc:697 -#: ../lib/modules/asteriskAccount.inc:1001 -#: ../lib/modules/asteriskAccount.inc:1245 +#: ../lib/modules/asteriskAccount.inc:1002 +#: ../lib/modules/asteriskAccount.inc:1246 msgid "RTP timeout" msgstr "RTP-Timeout" @@ -12790,17 +12797,17 @@ msgstr "Bereich bis" msgid "Ranges" msgstr "Bereiche" -#: ../templates/config/confmain.php:297 +#: ../templates/config/confmain.php:301 msgid "Read" msgstr "Lesen" -#: ../templates/config/confmain.php:213 ../templates/config/conftypes.php:268 +#: ../templates/config/confmain.php:217 ../templates/config/conftypes.php:268 #: ../lib/modules/customFields.inc:109 ../lib/modules/customFields.inc:1978 #: ../help/help.inc:194 msgid "Read-only" msgstr "Nur lesen" -#: ../lib/modules/inetOrgPerson.inc:3900 +#: ../lib/modules/inetOrgPerson.inc:3901 msgid "Read-only fields" msgstr "\"Nur lesen\"-Felder" @@ -12826,7 +12833,7 @@ msgstr "Diesen Attributwert wirklich löschen?" msgid "Realm" msgstr "Bereich" -#: ../lib/modules/nisMailAliasUser.inc:352 ../lib/modules/nisMailAlias.inc:180 +#: ../lib/modules/nisMailAliasUser.inc:344 ../lib/modules/nisMailAlias.inc:181 msgid "Recipient" msgstr "Empfänger" @@ -12845,19 +12852,19 @@ msgstr "Empfänger ist ungültig!" #: ../lib/types/mailAlias.inc:95 ../lib/modules/nisMailAlias.inc:88 #: ../lib/modules/nisMailAlias.inc:107 ../lib/modules/nisMailAlias.inc:115 -#: ../lib/modules/nisMailAlias.inc:468 +#: ../lib/modules/nisMailAlias.inc:471 msgid "Recipient list" msgstr "Empfängerliste" -#: ../lib/modules/nisMailAlias.inc:84 ../lib/modules/nisMailAlias.inc:183 -#: ../lib/modules/nisMailAlias.inc:201 +#: ../lib/modules/nisMailAlias.inc:84 ../lib/modules/nisMailAlias.inc:184 +#: ../lib/modules/nisMailAlias.inc:205 msgid "Recipients" msgstr "Empfänger" #: ../lib/modules/sambaSamAccount.inc:246 #: ../lib/modules/sambaSamAccount.inc:389 -#: ../lib/modules/sambaSamAccount.inc:1518 -#: ../lib/modules/sambaSamAccount.inc:1994 +#: ../lib/modules/sambaSamAccount.inc:1579 +#: ../lib/modules/sambaSamAccount.inc:2041 msgid "Reconnect if disconnected" msgstr "Wiederherstellen falls unterbrochen" @@ -12890,14 +12897,14 @@ msgstr "Kopiert alle Kindeinträge rekursiv." #: ../templates/config/profmanage.php:207 #: ../templates/config/profmanage.php:262 -#: ../templates/config/mainmanage.php:484 ../templates/config/confmain.php:507 +#: ../templates/config/mainmanage.php:484 ../templates/config/confmain.php:522 #: ../lib/modules/selfRegistration.inc:417 -#: ../lib/modules/sambaSamAccount.inc:2307 ../lib/modules/posixAccount.inc:3206 -#: ../lib/modules/windowsUser.inc:2916 +#: ../lib/modules/sambaSamAccount.inc:2354 ../lib/modules/posixAccount.inc:3165 +#: ../lib/modules/windowsUser.inc:2888 msgid "Reenter password" msgstr "Passwort nochmal eingeben" -#: ../templates/config/confmain.php:232 ../help/help.inc:202 +#: ../templates/config/confmain.php:236 ../help/help.inc:202 msgid "Referential integrity overlay" msgstr "Overlay für referentielle Integrität" @@ -12910,7 +12917,7 @@ msgstr "Referrals" #: ../templates/3rdParty/pla/lib/TemplateRender.php:984 #: ../templates/3rdParty/pla/lib/TemplateRender.php:985 #: ../templates/3rdParty/pla/lib/HTMLTree.php:237 -#: ../lib/modules/asteriskExtension.inc:456 ../lib/lists.inc:897 +#: ../lib/modules/asteriskExtension.inc:454 ../lib/lists.inc:888 msgid "Refresh" msgstr "Aktualisieren" @@ -12933,12 +12940,16 @@ msgstr "Aktualisiere Baum" msgid "Register new account" msgstr "Neuen Benutzer registrieren" +#: ../lib/2factor.inc:542 +msgid "Register new key" +msgstr "Neuen Schlüssel registrieren" + #: ../lib/modules/inetOrgPerson.inc:90 ../lib/modules/inetOrgPerson.inc:160 #: ../lib/modules/inetOrgPerson.inc:331 ../lib/modules/inetOrgPerson.inc:501 #: ../lib/modules/inetOrgPerson.inc:626 ../lib/modules/inetOrgPerson.inc:1335 -#: ../lib/modules/inetOrgPerson.inc:1956 ../lib/modules/inetOrgPerson.inc:2024 -#: ../lib/modules/inetOrgPerson.inc:2669 ../lib/modules/inetOrgPerson.inc:3865 -#: ../lib/modules/inetOrgPerson.inc:3906 +#: ../lib/modules/inetOrgPerson.inc:1957 ../lib/modules/inetOrgPerson.inc:2025 +#: ../lib/modules/inetOrgPerson.inc:2670 ../lib/modules/inetOrgPerson.inc:3866 +#: ../lib/modules/inetOrgPerson.inc:3907 msgid "Registered address" msgstr "Meldeadresse" @@ -12950,8 +12961,8 @@ msgstr "Meldeadresse, Stadt" #: ../lib/modules/asteriskAccount.inc:290 #: ../lib/modules/asteriskAccount.inc:499 #: ../lib/modules/asteriskAccount.inc:723 -#: ../lib/modules/asteriskAccount.inc:1007 -#: ../lib/modules/asteriskAccount.inc:1251 +#: ../lib/modules/asteriskAccount.inc:1008 +#: ../lib/modules/asteriskAccount.inc:1252 msgid "Registration context" msgstr "Registrierungskontext" @@ -12959,8 +12970,8 @@ msgstr "Registrierungskontext" #: ../lib/modules/asteriskAccount.inc:291 #: ../lib/modules/asteriskAccount.inc:506 #: ../lib/modules/asteriskAccount.inc:727 -#: ../lib/modules/asteriskAccount.inc:1008 -#: ../lib/modules/asteriskAccount.inc:1252 +#: ../lib/modules/asteriskAccount.inc:1009 +#: ../lib/modules/asteriskAccount.inc:1253 msgid "Registration extension" msgstr "Registrierungserweiterung" @@ -12968,8 +12979,8 @@ msgstr "Registrierungserweiterung" #: ../lib/modules/asteriskAccount.inc:295 #: ../lib/modules/asteriskAccount.inc:535 #: ../lib/modules/asteriskAccount.inc:752 -#: ../lib/modules/asteriskAccount.inc:1012 -#: ../lib/modules/asteriskAccount.inc:1256 +#: ../lib/modules/asteriskAccount.inc:1013 +#: ../lib/modules/asteriskAccount.inc:1257 msgid "Registration server" msgstr "Registrierungsserver" @@ -12994,16 +13005,13 @@ msgid "Remote server" msgstr "Remoteserver" #: ../templates/pdfedit/pdfpage.php:270 ../templates/pdfedit/pdfpage.php:345 -#: ../lib/modules/locking389ds.inc:166 ../lib/modules/mitKerberos.inc:692 -#: ../lib/modules/qmailUser.inc:881 ../lib/modules/windowsHost.inc:158 -#: ../lib/modules/sambaSamAccount.inc:1437 ../lib/modules/windowsUser.inc:1678 -#: ../lib/modules/windowsUser.inc:2133 ../lib/modules/kolabUser.inc:723 -#: ../lib/modules/shadowAccount.inc:492 ../lib/modules/shadowAccount.inc:545 -#: ../lib/modules/freeRadius.inc:565 ../lib/modules/sudoRole.inc:317 -#: ../lib/modules/sudoRole.inc:344 ../lib/modules/sudoRole.inc:371 -#: ../lib/modules/sudoRole.inc:398 ../lib/modules/sudoRole.inc:425 -#: ../lib/modules/sudoRole.inc:452 ../lib/modules/heimdalKerberos.inc:613 -#: ../lib/modules/windowsGroup.inc:360 +#: ../lib/modules/locking389ds.inc:182 ../lib/modules/mitKerberos.inc:714 +#: ../lib/modules/qmailUser.inc:721 ../lib/modules/windowsHost.inc:160 +#: ../lib/modules/sambaSamAccount.inc:1496 ../lib/modules/windowsUser.inc:1689 +#: ../lib/modules/windowsUser.inc:2105 ../lib/modules/kolabUser.inc:723 +#: ../lib/modules/shadowAccount.inc:506 ../lib/modules/shadowAccount.inc:572 +#: ../lib/modules/freeRadius.inc:565 ../lib/modules/heimdalKerberos.inc:613 +#: ../lib/modules/windowsGroup.inc:358 msgid "Remove" msgstr "Löschen" @@ -13031,41 +13039,41 @@ msgstr "FreeRadius-Erweiterung entfernen" msgid "Remove IP address extension" msgstr "IP-Adressen-Erweiterung entfernen" -#: ../lib/modules/mitKerberos.inc:463 ../lib/modules/heimdalKerberos.inc:389 +#: ../lib/modules/mitKerberos.inc:469 ../lib/modules/heimdalKerberos.inc:389 msgid "Remove Kerberos extension" msgstr "Kerberos-Erweiterung entfernen" -#: ../lib/modules/kopanoServer.inc:209 ../lib/modules/kopanoGroup.inc:261 -#: ../lib/modules/kopanoContact.inc:255 ../lib/modules/kopanoUser.inc:687 +#: ../lib/modules/kopanoServer.inc:190 ../lib/modules/kopanoGroup.inc:261 +#: ../lib/modules/kopanoContact.inc:255 ../lib/modules/kopanoUser.inc:641 msgid "Remove Kopano extension" msgstr "Kopano-Erweiterung entfernen" -#: ../lib/modules/puppetClient.inc:291 +#: ../lib/modules/puppetClient.inc:288 msgid "Remove Puppet extension" msgstr "Puppet-Erweiterung entfernen" -#: ../lib/modules/pykotaGroup.inc:210 ../lib/modules/pykotaUser.inc:382 +#: ../lib/modules/pykotaGroup.inc:210 ../lib/modules/pykotaUser.inc:383 msgid "Remove PyKota extension" msgstr "PyKota-Erweiterung entfernen" -#: ../lib/modules/ldapPublicKey.inc:156 +#: ../lib/modules/ldapPublicKey.inc:155 msgid "Remove SSH public key extension" msgstr "SSH-Schlüssel-Erweiterung entfernen" -#: ../lib/modules/sambaSamAccount.inc:1251 -#: ../lib/modules/sambaGroupMapping.inc:398 +#: ../lib/modules/sambaSamAccount.inc:1307 +#: ../lib/modules/sambaGroupMapping.inc:405 msgid "Remove Samba 3 extension" msgstr "Samba 3-Erweiterung entfernen" -#: ../lib/modules/shadowAccount.inc:380 +#: ../lib/modules/shadowAccount.inc:382 msgid "Remove Shadow account extension" msgstr "Shadow-Erweiterung entfernen" -#: ../lib/modules/posixGroup.inc:282 ../lib/modules/posixAccount.inc:1718 +#: ../lib/modules/posixGroup.inc:265 ../lib/modules/posixAccount.inc:1698 msgid "Remove Unix extension" msgstr "Unix-Erweiterung entfernen" -#: ../lib/modules/yubiKeyUser.inc:153 +#: ../lib/modules/yubiKeyUser.inc:156 msgid "Remove YubiKey extension" msgstr "YubiKey-Erweiterung entfernen" @@ -13078,11 +13086,11 @@ msgstr "Zarafa-Erweiterung entfernen" msgid "Remove extension" msgstr "Erweiterung entfernen" -#: ../lib/types/user.inc:476 +#: ../lib/types/user.inc:477 msgid "Remove from all Unix groups" msgstr "Aus allen Unix-Gruppen entfernen" -#: ../lib/types/user.inc:480 +#: ../lib/types/user.inc:481 msgid "Remove from all group of (unique) names" msgstr "Aus allen Namensgruppen entfernen" @@ -13094,26 +13102,26 @@ msgstr "Host-Erweiterung entfernen" msgid "Remove mail routing extension" msgstr "Mail-Routing-Erweiterung entfernen" -#: ../lib/modules/posixGroup.inc:244 ../lib/modules/posixAccount.inc:1711 -#: ../lib/modules/inetOrgPerson.inc:1570 +#: ../lib/modules/posixGroup.inc:228 ../lib/modules/posixAccount.inc:1692 +#: ../lib/modules/inetOrgPerson.inc:1571 msgid "Remove password" msgstr "Passwort entfernen" -#: ../lib/modules/passwordSelfReset.inc:372 +#: ../lib/modules/passwordSelfReset.inc:371 msgid "Remove password self reset extension" msgstr "Erweiterung für Passwortrücksetzung entfernen" -#: ../lib/modules/qmailUser.inc:671 ../lib/modules/qmailGroup.inc:592 +#: ../lib/modules/qmailUser.inc:543 ../lib/modules/qmailGroup.inc:491 msgid "Remove qmail extension" msgstr "Qmail-Erweiterung entfernen" #: ../templates/3rdParty/pla/htdocs/modify_member_form.php:147 #: ../lib/modules/zarafaContact.inc:450 ../lib/modules/zarafaGroup.inc:464 #: ../lib/modules/device.inc:273 ../lib/modules/kopanoGroup.inc:415 -#: ../lib/modules/zarafaUser.inc:953 ../lib/modules/kopanoContact.inc:403 -#: ../lib/modules/inetOrgPerson.inc:1781 ../lib/modules/kopanoUser.inc:953 -#: ../lib/modules/qmailGroup.inc:817 ../lib/modules/organizationalRole.inc:371 -#: ../lib/modules/windowsGroup.inc:705 ../lib/modules/groupOfNames.inc:400 +#: ../lib/modules/zarafaUser.inc:953 ../lib/modules/kopanoContact.inc:401 +#: ../lib/modules/inetOrgPerson.inc:1782 ../lib/modules/kopanoUser.inc:875 +#: ../lib/modules/qmailGroup.inc:670 ../lib/modules/organizationalRole.inc:371 +#: ../lib/modules/windowsGroup.inc:685 ../lib/modules/groupOfNames.inc:400 #: ../lib/modules/groupOfNames.inc:549 msgid "Remove selected entries" msgstr "Ausgewählte Einträge entfernen" @@ -13122,12 +13130,12 @@ msgstr "Ausgewählte Einträge entfernen" msgid "Remove this account type" msgstr "Diesen Accounttyp entfernen" -#: ../lib/modules/nisMailAliasUser.inc:153 -#: ../lib/modules/nisMailAliasUser.inc:190 +#: ../lib/modules/nisMailAliasUser.inc:152 +#: ../lib/modules/nisMailAliasUser.inc:184 msgid "Remove user from alias entry." msgstr "Benutzer aus Aliaseintrag entfernen." -#: ../lib/modules/posixGroup.inc:1059 ../lib/modules/posixGroup.inc:1102 +#: ../lib/modules/posixGroup.inc:1007 ../lib/modules/posixGroup.inc:1050 msgid "Removed users" msgstr "Entfernte Benutzer" @@ -13176,7 +13184,7 @@ msgstr "Benenne um" #: ../lib/modules/mitKerberos.inc:131 ../lib/modules/mitKerberos.inc:195 #: ../lib/modules/mitKerberos.inc:235 ../lib/modules/mitKerberos.inc:252 -#: ../lib/modules/mitKerberos.inc:357 ../lib/modules/mitKerberos.inc:788 +#: ../lib/modules/mitKerberos.inc:357 ../lib/modules/mitKerberos.inc:810 #: ../lib/modules/heimdalKerberos.inc:113 #: ../lib/modules/heimdalKerberos.inc:164 #: ../lib/modules/heimdalKerberos.inc:204 @@ -13193,7 +13201,7 @@ msgid "Renewable lifetime must be a number." msgstr "Laufzeitverlängerung muss eine Zahl sein." #: ../templates/lists/changePassword.php:359 -#: ../lib/modules/passwordSelfReset.inc:1304 ../lib/modules.inc:1118 +#: ../lib/modules/passwordSelfReset.inc:1302 ../lib/modules.inc:1119 msgid "Repeat password" msgstr "Passwort wiederholen" @@ -13201,25 +13209,25 @@ msgstr "Passwort wiederholen" msgid "Replaced $user or $group in homedir." msgstr "$user und $group wurden im Heimatverzeichnis ersetzt." -#: ../templates/config/confmain.php:356 ../lib/passwordExpirationJob.inc:84 -#: ../lib/passwordExpirationJob.inc:128 ../help/help.inc:339 -#: ../help/help.inc:376 +#: ../templates/config/confmain.php:360 ../lib/passwordExpirationJob.inc:84 +#: ../lib/passwordExpirationJob.inc:128 ../help/help.inc:345 +#: ../help/help.inc:382 msgid "Reply-to address" msgstr "Antwort-An Adresse" -#: ../templates/config/confmain.php:603 +#: ../templates/config/confmain.php:618 msgid "Reply-to address for password mails is invalid." msgstr "Antwort-An Adresse für Passwortmails ist ungültig." -#: ../lib/types/ppolicyType.inc:92 ../lib/modules/ppolicy.inc:128 -#: ../lib/modules/ppolicy.inc:154 ../lib/modules/ppolicy.inc:283 -#: ../lib/modules/ppolicy.inc:499 +#: ../lib/types/ppolicyType.inc:92 ../lib/modules/ppolicy.inc:127 +#: ../lib/modules/ppolicy.inc:153 ../lib/modules/ppolicy.inc:276 +#: ../lib/modules/ppolicy.inc:492 msgid "Require password change on first login" msgstr "Passwortänderung beim ersten Login" #: ../lib/modules/windowsUser.inc:223 ../lib/modules/windowsUser.inc:554 #: ../lib/modules/windowsUser.inc:860 ../lib/modules/windowsUser.inc:1225 -#: ../lib/modules/windowsUser.inc:2766 +#: ../lib/modules/windowsUser.inc:2738 msgid "Require smartcard" msgstr "Smartcard erforderlich" @@ -13236,46 +13244,46 @@ msgstr "Erforderliches Attribut für Objektklassen" msgid "Required attributes" msgstr "Vorgeschriebene Attribute" -#: ../lib/modules/mitKerberos.inc:179 ../lib/modules/mitKerberos.inc:453 +#: ../lib/modules/mitKerberos.inc:179 ../lib/modules/mitKerberos.inc:459 msgid "Requires hardware authentication" msgstr "Authentifizierung mit separater Hardware erforderlich" -#: ../lib/modules/mitKerberos.inc:176 ../lib/modules/mitKerberos.inc:448 +#: ../lib/modules/mitKerberos.inc:176 ../lib/modules/mitKerberos.inc:454 #: ../lib/modules/heimdalKerberos.inc:150 #: ../lib/modules/heimdalKerberos.inc:380 msgid "Requires preauthentication" msgstr "Vorauthentifizierung erforderlich" -#: ../lib/modules/sambaSamAccount.inc:1245 -#: ../lib/modules/pykotaBillingCode.inc:93 -#: ../lib/modules/pykotaBillingCode.inc:163 +#: ../lib/modules/sambaSamAccount.inc:1299 +#: ../lib/modules/pykotaBillingCode.inc:92 +#: ../lib/modules/pykotaBillingCode.inc:166 msgid "Reset" msgstr "Zurücksetzen" -#: ../lib/modules.inc:1298 +#: ../lib/modules.inc:1300 msgid "Reset changes" msgstr "Änderungen zurücksetzen" #: ../lib/modules/sambaSamAccount.inc:266 -#: ../lib/modules/sambaSamAccount.inc:1244 +#: ../lib/modules/sambaSamAccount.inc:1297 msgid "Reset password" msgstr "Passwort zurücksetzen" -#: ../lib/modules/sambaDomain.inc:137 ../lib/modules/sambaDomain.inc:199 -#: ../lib/modules/sambaDomain.inc:224 ../lib/modules/sambaDomain.inc:348 -#: ../lib/modules/sambaDomain.inc:605 +#: ../lib/modules/sambaDomain.inc:136 ../lib/modules/sambaDomain.inc:198 +#: ../lib/modules/sambaDomain.inc:223 ../lib/modules/sambaDomain.inc:341 +#: ../lib/modules/sambaDomain.inc:602 msgid "Reset time after lockout" msgstr "Rücksetzzeit nach Sperrung" -#: ../lib/modules/sambaDomain.inc:224 +#: ../lib/modules/sambaDomain.inc:223 msgid "Reset time after lockout must be are natural number." msgstr "Rücksetzzeit muss eine natürliche Zahl sein." -#: ../lib/modules/pykotaBillingCode.inc:94 +#: ../lib/modules/pykotaBillingCode.inc:93 msgid "Resets the billing code's balance and page counter to 0." msgstr "Den Kontostand des Rechnungscodes und Seitenzähler auf 0 zurücksetzen." -#: ../lib/modules/zarafaUser.inc:553 ../lib/modules/kopanoUser.inc:553 +#: ../lib/modules/zarafaUser.inc:553 ../lib/modules/kopanoUser.inc:522 msgid "Resource settings" msgstr "Ressourceneinstellungen" @@ -13283,8 +13291,8 @@ msgstr "Ressourceneinstellungen" #: ../lib/modules/asteriskAccount.inc:283 #: ../lib/modules/asteriskAccount.inc:448 #: ../lib/modules/asteriskAccount.inc:693 -#: ../lib/modules/asteriskAccount.inc:1000 -#: ../lib/modules/asteriskAccount.inc:1244 +#: ../lib/modules/asteriskAccount.inc:1001 +#: ../lib/modules/asteriskAccount.inc:1245 msgid "Restrict caller ID" msgstr "Anrufer-ID ausblenden" @@ -13305,7 +13313,7 @@ msgstr "Ergebnis" msgid "Retrieving DN" msgstr "Lade DN" -#: ../lib/modules/locking389ds.inc:90 +#: ../lib/modules/locking389ds.inc:91 msgid "Retry count" msgstr "Anzahl Versuche" @@ -13325,7 +13333,7 @@ msgstr "Reverse-DNS-Einträge (\"PTR\"-Einträge)" msgid "Reverse zone name" msgstr "Namen der Reverse-Zone" -#: ../templates/config/confmain.php:289 ../help/help.inc:124 +#: ../templates/config/confmain.php:293 ../help/help.inc:124 msgid "Rights for the home directory" msgstr "Rechte für das Heimatverzeichnis" @@ -13333,7 +13341,7 @@ msgstr "Rechte für das Heimatverzeichnis" msgid "Role" msgstr "Rolle" -#: ../lib/types/gon.inc:206 ../lib/types/group.inc:258 +#: ../lib/types/gon.inc:206 ../lib/types/group.inc:256 #, php-format msgid "Role count: %s" msgstr "Anzahl Rollen: %s" @@ -13344,8 +13352,8 @@ msgstr "DNs der Rollenmitglieder" #: ../lib/types/gon.inc:107 ../lib/types/group.inc:127 ../lib/types/sudo.inc:80 #: ../lib/modules/sudoRole.inc:65 ../lib/modules/sudoRole.inc:145 -#: ../lib/modules/sudoRole.inc:213 ../lib/modules/sudoRole.inc:298 -#: ../lib/modules/sudoRole.inc:745 +#: ../lib/modules/sudoRole.inc:213 ../lib/modules/sudoRole.inc:296 +#: ../lib/modules/sudoRole.inc:452 msgid "Role name" msgstr "Rollenname" @@ -13361,16 +13369,16 @@ msgid "Roles" msgstr "Rollen" #: ../lib/modules/zarafaUser.inc:558 ../lib/modules/zarafaUser.inc:1287 -#: ../lib/modules/kopanoUser.inc:558 ../lib/modules/kopanoUser.inc:1287 +#: ../lib/modules/kopanoUser.inc:527 ../lib/modules/kopanoUser.inc:1206 msgid "Room" msgstr "Raum" #: ../lib/modules/inetOrgPerson.inc:162 ../lib/modules/inetOrgPerson.inc:413 #: ../lib/modules/inetOrgPerson.inc:507 ../lib/modules/inetOrgPerson.inc:701 #: ../lib/modules/inetOrgPerson.inc:1372 ../lib/modules/inetOrgPerson.inc:1375 -#: ../lib/modules/inetOrgPerson.inc:1964 ../lib/modules/inetOrgPerson.inc:2030 -#: ../lib/modules/inetOrgPerson.inc:2702 ../lib/modules/inetOrgPerson.inc:3867 -#: ../lib/modules/inetOrgPerson.inc:3907 +#: ../lib/modules/inetOrgPerson.inc:1965 ../lib/modules/inetOrgPerson.inc:2031 +#: ../lib/modules/inetOrgPerson.inc:2703 ../lib/modules/inetOrgPerson.inc:3868 +#: ../lib/modules/inetOrgPerson.inc:3908 msgid "Room number" msgstr "Raumnummer" @@ -13397,7 +13405,7 @@ msgstr "Zeilen" msgid "Rules" msgstr "Regeln" -#: ../lib/modules/customScripts.inc:206 +#: ../lib/modules/customScripts.inc:211 msgid "Run" msgstr "Ausführen" @@ -13407,8 +13415,8 @@ msgstr "Ausführgruppe" #: ../lib/modules/sudoRole.inc:89 ../lib/modules/sudoRole.inc:117 #: ../lib/modules/sudoRole.inc:182 ../lib/modules/sudoRole.inc:219 -#: ../lib/modules/sudoRole.inc:231 ../lib/modules/sudoRole.inc:423 -#: ../lib/modules/sudoRole.inc:751 +#: ../lib/modules/sudoRole.inc:231 ../lib/modules/sudoRole.inc:308 +#: ../lib/modules/sudoRole.inc:458 msgid "Run groups" msgstr "Ausführgruppen" @@ -13418,8 +13426,8 @@ msgstr "Ausführbenutzer" #: ../lib/modules/sudoRole.inc:85 ../lib/modules/sudoRole.inc:112 #: ../lib/modules/sudoRole.inc:176 ../lib/modules/sudoRole.inc:218 -#: ../lib/modules/sudoRole.inc:230 ../lib/modules/sudoRole.inc:396 -#: ../lib/modules/sudoRole.inc:750 +#: ../lib/modules/sudoRole.inc:230 ../lib/modules/sudoRole.inc:306 +#: ../lib/modules/sudoRole.inc:457 msgid "Run users" msgstr "Ausführbenutzer" @@ -13471,7 +13479,7 @@ msgstr "SRV-Eintrag" msgid "SRV records" msgstr "SRV-Einträge" -#: ../templates/tests/lamdaemonTest.php:246 +#: ../templates/tests/lamdaemonTest.php:247 msgid "SSH connection" msgstr "SSH-Verbindung" @@ -13479,23 +13487,23 @@ msgstr "SSH-Verbindung" msgid "SSH connection established." msgstr "SSH-Verbindung aufgebaut." -#: ../templates/config/confmain.php:282 ../templates/config/confmain.php:703 +#: ../templates/config/confmain.php:286 ../templates/config/confmain.php:718 #: ../help/help.inc:228 msgid "SSH key file" msgstr "SSH Schlüsseldatei" -#: ../templates/config/confmain.php:283 ../help/help.inc:230 +#: ../templates/config/confmain.php:287 ../help/help.inc:230 msgid "SSH key password" msgstr "SSH-Schlüsselpasswort" #: ../lib/modules/ldapPublicKey.inc:75 ../lib/modules/ldapPublicKey.inc:85 #: ../lib/modules/ldapPublicKey.inc:89 ../lib/modules/ldapPublicKey.inc:109 -#: ../lib/modules/ldapPublicKey.inc:142 +#: ../lib/modules/ldapPublicKey.inc:141 msgid "SSH public key" msgstr "SSH-Schlüssel" #: ../lib/modules/ldapPublicKey.inc:116 ../lib/modules/ldapPublicKey.inc:120 -#: ../lib/modules/ldapPublicKey.inc:285 ../lib/modules/ldapPublicKey.inc:327 +#: ../lib/modules/ldapPublicKey.inc:283 ../lib/modules/ldapPublicKey.inc:325 msgid "SSH public keys" msgstr "SSH-Schlüssel" @@ -13507,9 +13515,9 @@ msgstr "SSL-Zertifikat" msgid "SSL certificates" msgstr "SSL-Zertifikate" -#: ../lib/modules/kopanoServer.inc:72 ../lib/modules/kopanoServer.inc:106 -#: ../lib/modules/kopanoServer.inc:136 ../lib/modules/kopanoServer.inc:152 -#: ../lib/modules/kopanoServer.inc:180 ../lib/modules/kopanoServer.inc:328 +#: ../lib/modules/kopanoServer.inc:71 ../lib/modules/kopanoServer.inc:105 +#: ../lib/modules/kopanoServer.inc:135 ../lib/modules/kopanoServer.inc:151 +#: ../lib/modules/kopanoServer.inc:170 ../lib/modules/kopanoServer.inc:308 #: ../lib/modules/zarafaServer.inc:72 ../lib/modules/zarafaServer.inc:106 #: ../lib/modules/zarafaServer.inc:136 ../lib/modules/zarafaServer.inc:152 #: ../lib/modules/zarafaServer.inc:180 ../lib/modules/zarafaServer.inc:328 @@ -13521,14 +13529,14 @@ msgstr "SSL-Port" msgid "Samba" msgstr "Samba" -#: ../lib/types/user.inc:310 ../lib/types/user.inc:460 -#: ../lib/types/user.inc:499 ../lib/types/user.inc:1094 +#: ../lib/types/user.inc:311 ../lib/types/user.inc:461 +#: ../lib/types/user.inc:500 ../lib/types/user.inc:1095 #: ../lib/modules/sambaSamAccount.inc:146 #: ../lib/modules/sambaGroupMapping.inc:101 msgid "Samba 3" msgstr "Samba 3" -#: ../lib/modules/sambaDomain.inc:210 ../lib/modules/sambaDomain.inc:211 +#: ../lib/modules/sambaDomain.inc:209 ../lib/modules/sambaDomain.inc:210 msgid "Samba 3 domain SID is invalid!" msgstr "Samba 3 Domänen-SID ist ungültig!" @@ -13536,14 +13544,14 @@ msgstr "Samba 3 Domänen-SID ist ungültig!" msgid "Samba 3 domain entries" msgstr "Samba 3 Domäneneinträge" -#: ../lib/modules/posixGroup.inc:600 ../lib/modules/posixAccount.inc:2184 +#: ../lib/modules/posixGroup.inc:549 ../lib/modules/posixAccount.inc:2142 msgid "Samba ID pool" msgstr "Samba-ID-Pool" -#: ../lib/modules/posixGroup.inc:514 ../lib/modules/posixGroup.inc:625 -#: ../lib/modules/posixGroup.inc:766 ../lib/modules/posixAccount.inc:121 -#: ../lib/modules/posixAccount.inc:385 ../lib/modules/posixAccount.inc:2231 -#: ../lib/modules/posixAccount.inc:2302 +#: ../lib/modules/posixGroup.inc:463 ../lib/modules/posixGroup.inc:574 +#: ../lib/modules/posixGroup.inc:715 ../lib/modules/posixAccount.inc:121 +#: ../lib/modules/posixAccount.inc:390 ../lib/modules/posixAccount.inc:2189 +#: ../lib/modules/posixAccount.inc:2260 msgid "Samba ID pool DN" msgstr "DN des Samba-ID-Pools" @@ -13563,7 +13571,7 @@ msgstr "Samba RID-Nummer" msgid "Samba display name" msgstr "Samba Anzeigename" -#: ../lib/modules/sambaDomain.inc:58 +#: ../lib/modules/sambaDomain.inc:57 msgid "Samba domain" msgstr "Samba-Domäne" @@ -13578,7 +13586,7 @@ msgstr "Samba-Domänen" #: ../lib/modules/sambaGroupMapping.inc:113 #: ../lib/modules/sambaGroupMapping.inc:135 #: ../lib/modules/sambaGroupMapping.inc:162 -#: ../lib/modules/sambaGroupMapping.inc:517 +#: ../lib/modules/sambaGroupMapping.inc:514 msgid "Samba group type" msgstr "Samba-Gruppentyp" @@ -13591,29 +13599,29 @@ msgstr "Samba-Passwort" #: ../lib/modules/sambaSamAccount.inc:316 #: ../lib/modules/sambaSamAccount.inc:319 #: ../lib/modules/sambaSamAccount.inc:526 -#: ../lib/modules/sambaSamAccount.inc:1185 -#: ../lib/modules/sambaSamAccount.inc:1736 -#: ../lib/modules/sambaSamAccount.inc:1908 -#: ../lib/modules/sambaSamAccount.inc:1927 +#: ../lib/modules/sambaSamAccount.inc:1227 +#: ../lib/modules/sambaSamAccount.inc:1783 +#: ../lib/modules/sambaSamAccount.inc:1955 +#: ../lib/modules/sambaSamAccount.inc:1974 msgid "Samba workstations" msgstr "Samba PCs" -#: ../lib/modules/sambaSamAccount.inc:1337 +#: ../lib/modules/sambaSamAccount.inc:1387 msgid "Saturday" msgstr "Samstag" #: ../templates/config/confmodules.php:150 ../templates/config/jobList.php:182 -#: ../templates/config/jobs.php:313 ../templates/config/confmain.php:521 +#: ../templates/config/jobs.php:313 ../templates/config/confmain.php:536 #: ../templates/config/moduleSettings.php:184 #: ../templates/config/conftypes.php:313 -#: ../templates/selfService/selfServiceMain.php:454 -#: ../templates/selfService/adminMain.php:809 +#: ../templates/selfService/selfServiceMain.php:455 +#: ../templates/selfService/adminMain.php:821 #: ../templates/pdfedit/pdfpage.php:448 #: ../templates/profedit/profilepage.php:242 #: ../templates/3rdParty/pla/lib/TemplateRender.php:1120 #: ../templates/3rdParty/pla/lib/TemplateRender.php:1124 -#: ../lib/modules/inetOrgPerson.inc:3074 ../lib/modules/customFields.inc:4409 -#: ../lib/modules.inc:1292 +#: ../lib/modules/inetOrgPerson.inc:3075 ../lib/modules/customFields.inc:4409 +#: ../lib/modules.inc:1293 msgid "Save" msgstr "Speichern" @@ -13659,15 +13667,15 @@ msgstr "Zugehörigkeitsbereiche" msgid "Script path" msgstr "Scriptpfad" -#: ../templates/config/confmain.php:654 +#: ../templates/config/confmain.php:669 msgid "Script path is invalid!" msgstr "Scriptpfad ist ungültig!" -#: ../templates/config/confmain.php:691 +#: ../templates/config/confmain.php:706 msgid "Script rights are invalid!" msgstr "Skriptrechte sind ungültig!" -#: ../templates/config/confmain.php:657 +#: ../templates/config/confmain.php:672 msgid "Script server is invalid!" msgstr "Scriptserver ist ungültig!" @@ -13699,7 +13707,7 @@ msgstr "Suchdomänen" #: ../templates/3rdParty/pla/lib/QueryRender.php:135 #: ../templates/3rdParty/pla/lib/export_functions.php:201 #: ../templates/3rdParty/pla/htdocs/export_form.php:81 ../lib/export.inc:256 -#: ../help/help.inc:363 +#: ../help/help.inc:369 msgid "Search filter" msgstr "Suchfilter" @@ -13714,16 +13722,16 @@ msgstr "Suche lieferte keine Ergebnisse" msgid "Search scope" msgstr "Suchbereich" -#: ../lib/modules/asteriskExtension.inc:455 +#: ../lib/modules/asteriskExtension.inc:453 msgid "Search tree suffix for users" msgstr "Suche Benutzer im Suffix für Baumansicht" -#: ../templates/config/confmain.php:491 -#: ../templates/selfService/adminMain.php:511 ../help/help.inc:320 +#: ../templates/config/confmain.php:504 +#: ../templates/selfService/adminMain.php:523 ../help/help.inc:324 msgid "Secret key" msgstr "Geheimer Schlüssel" -#: ../templates/selfService/adminMain.php:544 ../help/help.inc:314 +#: ../templates/selfService/adminMain.php:556 ../help/help.inc:318 msgid "Secure login" msgstr "Login schützen" @@ -13758,11 +13766,11 @@ msgstr "" #: ../lib/modules/passwordSelfReset.inc:83 #: ../lib/modules/passwordSelfReset.inc:294 -#: ../lib/modules/passwordSelfReset.inc:652 +#: ../lib/modules/passwordSelfReset.inc:650 msgid "Security questions" msgstr "Sicherheitsfragen" -#: ../templates/config/mainmanage.php:319 ../templates/config/confmain.php:417 +#: ../templates/config/mainmanage.php:319 ../templates/config/confmain.php:421 msgid "Security settings" msgstr "Sicherheitseinstellungen" @@ -13770,7 +13778,7 @@ msgstr "Sicherheitseinstellungen" msgid "See also" msgstr "Siehe auch" -#: ../lib/account.inc:992 +#: ../lib/account.inc:1004 msgid "See the manual for instructions to solve this problem." msgstr "Bitte lesen Sie das Handbuch um dieses Problem zu lösen." @@ -13786,7 +13794,7 @@ msgstr "Wählen Sie eine Vorlage zum Editieren" msgid "Select an LDIF file" msgstr "LDIF-Datei auswählen" -#: ../lib/modules/nisnetgroup.inc:247 ../lib/modules/nisnetgroup.inc:282 +#: ../lib/modules/nisnetgroup.inc:245 ../lib/modules/nisnetgroup.inc:285 msgid "Select host" msgstr "Host wählen" @@ -13794,7 +13802,7 @@ msgstr "Host wählen" msgid "Select list" msgstr "Auswahlliste" -#: ../lib/modules/nisMailAlias.inc:193 ../lib/modules/nisMailAlias.inc:214 +#: ../lib/modules/nisMailAlias.inc:196 ../lib/modules/nisMailAlias.inc:219 msgid "Select mail" msgstr "Email auswählen" @@ -13804,14 +13812,14 @@ msgstr "" "Bitte wählen Sie einen oder mehrere Aliaseinträge aus der Liste um sie dem " "Empfänger hinzuzufügen." -#: ../lib/modules/nisnetgroup.inc:253 ../lib/modules/nisnetgroup.inc:288 -#: ../lib/modules/nisMailAlias.inc:194 ../lib/modules/nisMailAlias.inc:215 +#: ../lib/modules/nisnetgroup.inc:254 ../lib/modules/nisnetgroup.inc:294 +#: ../lib/modules/nisMailAlias.inc:197 ../lib/modules/nisMailAlias.inc:220 msgid "Select user" msgstr "Benutzer wählen" -#: ../lib/modules/posixAccount.inc:1803 ../lib/modules/posixAccount.inc:1834 -#: ../lib/modules/windowsUser.inc:1770 ../lib/modules/nisnetgroup.inc:397 -#: ../lib/modules/groupOfNamesUser.inc:169 ../lib/modules/windowsGroup.inc:510 +#: ../lib/modules/posixAccount.inc:1781 ../lib/modules/posixAccount.inc:1812 +#: ../lib/modules/windowsUser.inc:1794 ../lib/modules/nisnetgroup.inc:411 +#: ../lib/modules/groupOfNamesUser.inc:169 ../lib/modules/windowsGroup.inc:528 msgid "Selected groups" msgstr "Gewählte Gruppen" @@ -13824,39 +13832,39 @@ msgstr "Ausgewählte Module" msgid "Selected roles" msgstr "Ausgewählte Rollen" -#: ../lib/modules/posixGroup.inc:324 ../lib/modules/asteriskExtension.inc:461 +#: ../lib/modules/posixGroup.inc:321 ../lib/modules/asteriskExtension.inc:459 msgid "Selected users" msgstr "Ausgewählte Benutzer" -#: ../templates/selfService/adminMain.php:330 +#: ../templates/selfService/adminMain.php:336 msgid "Self service configuration" msgstr "SelfService-Einstellungen" -#: ../templates/selfService/adminMain.php:354 +#: ../templates/selfService/adminMain.php:360 msgid "Self service configuration editor" msgstr "SelfService-Einstellungsassistent" -#: ../templates/selfService/adminMain.php:360 +#: ../templates/selfService/adminMain.php:366 msgid "Self service login" msgstr "SelfService-Login" -#: ../lib/modules/passwordSelfReset.inc:634 +#: ../lib/modules/passwordSelfReset.inc:632 msgid "Self service login attribute" msgstr "SelfService-Login-Attribut" -#: ../templates/selfService/adminMain.php:346 +#: ../templates/selfService/adminMain.php:352 msgid "Self service profile" msgstr "SelfService-Profil" #: ../lib/modules/passwordSelfReset.inc:133 -#: ../lib/modules/passwordSelfReset.inc:659 -#: ../lib/modules/passwordSelfReset.inc:660 +#: ../lib/modules/passwordSelfReset.inc:657 +#: ../lib/modules/passwordSelfReset.inc:658 msgid "Send confirmation mail" msgstr "Bestätigungsmail senden" #: ../lib/modules/passwordSelfReset.inc:115 -#: ../lib/modules/passwordSelfReset.inc:676 -#: ../lib/modules/passwordSelfReset.inc:677 +#: ../lib/modules/passwordSelfReset.inc:674 +#: ../lib/modules/passwordSelfReset.inc:675 msgid "Send notification mail" msgstr "Hinweismail senden" @@ -13866,29 +13874,29 @@ msgstr "Passwort per mail senden" #: ../templates/lists/changePassword.php:320 #: ../templates/lists/changePassword.php:364 -#: ../templates/config/confmain.php:341 ../lib/modules.inc:1143 -#: ../help/help.inc:260 +#: ../templates/config/confmain.php:345 ../lib/modules.inc:1144 +#: ../help/help.inc:262 msgid "Send via mail" msgstr "Per mail senden" #: ../lib/modules/qmailGroup.inc:185 ../lib/modules/qmailGroup.inc:189 #: ../lib/modules/qmailGroup.inc:318 ../lib/modules/qmailGroup.inc:373 -#: ../lib/modules/qmailGroup.inc:394 ../lib/modules/qmailGroup.inc:484 -#: ../lib/modules/qmailGroup.inc:985 ../lib/modules/qmailGroup.inc:1022 +#: ../lib/modules/qmailGroup.inc:394 ../lib/modules/qmailGroup.inc:426 +#: ../lib/modules/qmailGroup.inc:836 ../lib/modules/qmailGroup.inc:873 msgid "Sender email addresses" msgstr "Email-Adressen der Sender" #: ../lib/modules/qmailGroup.inc:137 ../lib/modules/qmailGroup.inc:141 #: ../lib/modules/qmailGroup.inc:260 ../lib/modules/qmailGroup.inc:352 -#: ../lib/modules/qmailGroup.inc:486 ../lib/modules/qmailGroup.inc:756 -#: ../lib/modules/qmailGroup.inc:984 ../lib/modules/qmailGroup.inc:1021 +#: ../lib/modules/qmailGroup.inc:428 ../lib/modules/qmailGroup.inc:608 +#: ../lib/modules/qmailGroup.inc:835 ../lib/modules/qmailGroup.inc:872 msgid "Sender entries" msgstr "Sender" #: ../lib/modules/qmailGroup.inc:153 ../lib/modules/qmailGroup.inc:157 #: ../lib/modules/qmailGroup.inc:276 ../lib/modules/qmailGroup.inc:358 -#: ../lib/modules/qmailGroup.inc:485 ../lib/modules/qmailGroup.inc:986 -#: ../lib/modules/qmailGroup.inc:1023 +#: ../lib/modules/qmailGroup.inc:427 ../lib/modules/qmailGroup.inc:837 +#: ../lib/modules/qmailGroup.inc:874 msgid "Sender filter" msgstr "Senderfilter" @@ -13906,7 +13914,7 @@ msgstr "" "Sendet dem Benutzer eine email um die email-Adresse zu verifizieren bevor " "die Passwortrücksetzung durchgeführt wird." -#: ../lib/modules/posixAccount.inc:443 +#: ../lib/modules/posixAccount.inc:448 msgid "" "Sends the old password together with the new password when the user sets a " "new password." @@ -13914,7 +13922,7 @@ msgstr "" "Sendet das alte Passwort zusammen mit dem neuen Passwort wenn der Benutzer " "das Passwort ändert." -#: ../lib/modules/inetOrgPerson.inc:671 ../help/help.inc:261 +#: ../lib/modules/inetOrgPerson.inc:671 ../help/help.inc:263 msgid "" "Sends the password to the user via mail. Please edit your LAM server profile " "to setup the mail settings." @@ -13946,18 +13954,18 @@ msgstr "Seriennummer" msgid "Server" msgstr "Server" -#: ../templates/config/confmain.php:189 -#: ../templates/selfService/adminMain.php:414 ../lib/modules/qmailUser.inc:130 +#: ../templates/config/confmain.php:193 +#: ../templates/selfService/adminMain.php:420 ../lib/modules/qmailUser.inc:130 #: ../lib/modules/qmailUser.inc:209 ../lib/modules/qmailUser.inc:315 -#: ../lib/modules/qmailUser.inc:381 ../lib/modules/qmailUser.inc:557 -#: ../lib/modules/qmailUser.inc:1099 ../lib/modules/qmailUser.inc:1221 +#: ../lib/modules/qmailUser.inc:381 ../lib/modules/qmailUser.inc:466 +#: ../lib/modules/qmailUser.inc:939 ../lib/modules/qmailUser.inc:1061 #: ../lib/modules/imapAccess.inc:81 ../lib/modules/imapAccess.inc:508 #: ../help/help.inc:62 msgid "Server address" msgstr "Serveradresse" -#: ../templates/config/confmain.php:549 -#: ../templates/selfService/adminMain.php:239 +#: ../templates/config/confmain.php:564 +#: ../templates/selfService/adminMain.php:245 msgid "Server address is invalid!" msgstr "Die Server-Adresse ist ungültig!" @@ -13965,7 +13973,7 @@ msgstr "Die Server-Adresse ist ungültig!" msgid "Server information" msgstr "Serverinformationen" -#: ../templates/config/confmain.php:278 +#: ../templates/config/confmain.php:282 msgid "Server list" msgstr "Serverliste" @@ -13974,8 +13982,8 @@ msgstr "Serverliste" msgid "Server profile" msgstr "Serverprofil" -#: ../templates/config/confmain.php:186 -#: ../templates/selfService/adminMain.php:412 +#: ../templates/config/confmain.php:190 +#: ../templates/selfService/adminMain.php:418 msgid "Server settings" msgstr "Servereinstellungen" @@ -13987,7 +13995,7 @@ msgstr "Serverstatistiken" msgid "Server time" msgstr "Serverzeit" -#: ../lib/modules/kopanoServer.inc:380 ../lib/modules/zarafaServer.inc:380 +#: ../lib/modules/kopanoServer.inc:360 ../lib/modules/zarafaServer.inc:380 msgid "Servers" msgstr "Server" @@ -14008,35 +14016,35 @@ msgstr "Zeitbeschränkung für Sitzung" msgid "Set" msgstr "Setzen" -#: ../lib/modules/sambaSamAccount.inc:1425 -#: ../lib/modules/sambaSamAccount.inc:1428 -#: ../lib/modules/sambaSamAccount.inc:1431 ../lib/modules/windowsUser.inc:1666 -#: ../lib/modules/windowsUser.inc:1669 ../lib/modules/windowsUser.inc:1672 -#: ../lib/modules/shadowAccount.inc:480 ../lib/modules/shadowAccount.inc:483 -#: ../lib/modules/shadowAccount.inc:486 +#: ../lib/modules/sambaSamAccount.inc:1483 +#: ../lib/modules/sambaSamAccount.inc:1486 +#: ../lib/modules/sambaSamAccount.inc:1489 ../lib/modules/windowsUser.inc:1676 +#: ../lib/modules/windowsUser.inc:1679 ../lib/modules/windowsUser.inc:1682 +#: ../lib/modules/shadowAccount.inc:493 ../lib/modules/shadowAccount.inc:496 +#: ../lib/modules/shadowAccount.inc:499 msgid "Set also for Kerberos" msgstr "Auch für Kerberos setzen" -#: ../lib/modules/mitKerberos.inc:683 ../lib/modules/shadowAccount.inc:474 +#: ../lib/modules/mitKerberos.inc:704 ../lib/modules/shadowAccount.inc:487 #: ../lib/modules/heimdalKerberos.inc:604 msgid "Set also for Samba 3" msgstr "Auch für Samba 3 setzen" -#: ../lib/modules/mitKerberos.inc:680 ../lib/modules/sambaSamAccount.inc:1422 -#: ../lib/modules/windowsUser.inc:1663 ../lib/modules/heimdalKerberos.inc:601 +#: ../lib/modules/mitKerberos.inc:701 ../lib/modules/sambaSamAccount.inc:1480 +#: ../lib/modules/windowsUser.inc:1673 ../lib/modules/heimdalKerberos.inc:601 msgid "Set also for Shadow" msgstr "Auch für Shadow setzen" -#: ../lib/modules/mitKerberos.inc:686 ../lib/modules/shadowAccount.inc:477 +#: ../lib/modules/mitKerberos.inc:707 ../lib/modules/shadowAccount.inc:490 #: ../lib/modules/heimdalKerberos.inc:607 msgid "Set also for Windows" msgstr "Auch für Windows setzen" -#: ../lib/modules.inc:1305 ../lib/modules.inc:1307 +#: ../lib/modules.inc:1308 ../lib/modules.inc:1311 msgid "Set password" msgstr "Passwort setzen" -#: ../lib/modules/posixAccount.inc:323 ../lib/modules/posixAccount.inc:2335 +#: ../lib/modules/posixAccount.inc:328 ../lib/modules/posixAccount.inc:2293 msgid "Set primary group as memberUid" msgstr "Setze primäre Gruppe als memberUid" @@ -14046,7 +14054,7 @@ msgstr "Setze primäre Gruppe als memberUid" msgid "Set profile password" msgstr "Profilpasswort setzen" -#: ../lib/modules.inc:1308 +#: ../lib/modules.inc:1312 msgid "Set random password" msgstr "Zufälliges Passwort setzen" @@ -14058,7 +14066,7 @@ msgstr "Eigenes Passwort setzen" msgid "Set the search results to 0 to retrieve all available records." msgstr "Setzen Sie den Wert auf 0 um alle möglichen Ergebnisse zu erhalten." -#: ../templates/selfService/adminMain.php:648 +#: ../templates/selfService/adminMain.php:660 msgid "Set this field read-only." msgstr "Dieses Feld schreibschützen." @@ -14103,7 +14111,7 @@ msgstr "" msgid "Sets the delivery mode (e.g. disable mail forwarding)." msgstr "Damit setzen Sie den Liefermodus (z.B. keine Weiterleitung)." -#: ../lib/modules/posixGroup.inc:486 +#: ../lib/modules/posixGroup.inc:435 msgid "Sets the group password." msgstr "Setzt das Gruppenpasswort." @@ -14146,17 +14154,17 @@ msgstr "" "Bei Aktivierung dieser Option sieht der Benutzer keine gemeinsamen Ordner " "mehr." -#: ../lib/types/user.inc:365 ../lib/types/user.inc:368 -#: ../lib/types/user.inc:1075 ../lib/types/user.inc:1078 -#: ../lib/modules/shadowAccount.inc:90 ../lib/modules/shadowAccount.inc:848 -#: ../lib/modules/shadowAccount.inc:951 ../lib/modules/shadowAccount.inc:1045 +#: ../lib/types/user.inc:366 ../lib/types/user.inc:369 +#: ../lib/types/user.inc:1076 ../lib/types/user.inc:1079 +#: ../lib/modules/shadowAccount.inc:90 ../lib/modules/shadowAccount.inc:875 +#: ../lib/modules/shadowAccount.inc:978 ../lib/modules/shadowAccount.inc:1072 msgid "Shadow" msgstr "Shadow" #: ../lib/modules/sambaSamAccount.inc:244 #: ../lib/modules/sambaSamAccount.inc:383 -#: ../lib/modules/sambaSamAccount.inc:1502 -#: ../lib/modules/sambaSamAccount.inc:1978 +#: ../lib/modules/sambaSamAccount.inc:1563 +#: ../lib/modules/sambaSamAccount.inc:2025 msgid "Shadowing" msgstr "Spiegeln" @@ -14173,8 +14181,8 @@ msgstr "Gemeinsamer EMailordner" #: ../lib/modules/zarafaUser.inc:381 ../lib/modules/zarafaUser.inc:615 #: ../lib/modules/zarafaUser.inc:1309 ../lib/modules/zarafaUser.inc:1825 #: ../lib/modules/kopanoUser.inc:130 ../lib/modules/kopanoUser.inc:297 -#: ../lib/modules/kopanoUser.inc:381 ../lib/modules/kopanoUser.inc:615 -#: ../lib/modules/kopanoUser.inc:1309 ../lib/modules/kopanoUser.inc:1836 +#: ../lib/modules/kopanoUser.inc:381 ../lib/modules/kopanoUser.inc:579 +#: ../lib/modules/kopanoUser.inc:1228 ../lib/modules/kopanoUser.inc:1755 msgid "Shared store only" msgstr "Nur gemeinsamer Speicher" @@ -14194,7 +14202,7 @@ msgstr "Anzeigen" msgid "Show LDIF file" msgstr "Zeige LDIF-Datei" -#: ../lib/types/user.inc:823 +#: ../lib/types/user.inc:824 msgid "Show account status" msgstr "Accountstatus anzeigen" @@ -14208,7 +14216,7 @@ msgid "Show calendar" msgstr "Kalender anzeigen" #: ../lib/modules/organizationalRole.inc:179 -#: ../lib/modules/windowsGroup.inc:382 ../lib/modules/groupOfNames.inc:230 +#: ../lib/modules/windowsGroup.inc:381 ../lib/modules/groupOfNames.inc:230 msgid "Show effective members" msgstr "Effektive Benutzer anzeigen" @@ -14216,7 +14224,7 @@ msgstr "Effektive Benutzer anzeigen" msgid "Show internal attributes" msgstr "Interne Attribute anzeigen" -#: ../lib/modules/asteriskExtension.inc:454 +#: ../lib/modules/asteriskExtension.inc:452 msgid "Show only Asterisk accounts" msgstr "Nur Asterisk-Accounts anzeigen" @@ -14232,7 +14240,7 @@ msgstr "Einzellizenz" msgid "Single valued" msgstr "Einzelwert" -#: ../lib/modules/pykotaUser.inc:617 ../lib/modules/pykotaUser.inc:1012 +#: ../lib/modules/pykotaUser.inc:607 ../lib/modules/pykotaUser.inc:994 msgid "Size" msgstr "Größe" @@ -14247,12 +14255,12 @@ msgstr "" msgid "Skip" msgstr "Überspringen" -#: ../lib/modules/quota.inc:743 ../lib/modules/systemQuotas.inc:371 +#: ../lib/modules/quota.inc:712 ../lib/modules/systemQuotas.inc:371 msgid "Soft block" msgstr "Weiches Block-Limit" -#: ../lib/modules/quota.inc:117 ../lib/modules/quota.inc:487 -#: ../lib/modules/quota.inc:593 ../lib/modules/systemQuotas.inc:126 +#: ../lib/modules/quota.inc:117 ../lib/modules/quota.inc:467 +#: ../lib/modules/quota.inc:564 ../lib/modules/systemQuotas.inc:126 msgid "Soft block limit" msgstr "Weiches Block-Limit" @@ -14260,7 +14268,7 @@ msgstr "Weiches Block-Limit" msgid "Soft block limit." msgstr "Weiches Block-Limit" -#: ../lib/modules/quota.inc:745 ../lib/modules/systemQuotas.inc:373 +#: ../lib/modules/quota.inc:714 ../lib/modules/systemQuotas.inc:373 msgid "Soft inode" msgstr "Weiches Inode-Limit" @@ -14268,8 +14276,8 @@ msgstr "Weiches Inode-Limit" msgid "Soft inode (files) limit." msgstr "Weiches Inode-Limit (Dateien)." -#: ../lib/modules/quota.inc:139 ../lib/modules/quota.inc:491 -#: ../lib/modules/quota.inc:599 ../lib/modules/systemQuotas.inc:130 +#: ../lib/modules/quota.inc:139 ../lib/modules/quota.inc:468 +#: ../lib/modules/quota.inc:570 ../lib/modules/systemQuotas.inc:130 msgid "Soft inode limit" msgstr "Weiches Inode-Limit" @@ -14304,7 +14312,7 @@ msgstr "" msgid "Sorry this help number ({bold}%s{endbold}) is not available." msgstr "Diese Hilfenummer ist leider nicht verfügbar: {bold}%s{endbold}" -#: ../lib/lists.inc:432 +#: ../lib/lists.inc:420 msgid "Sort sequence" msgstr "Sortierreihenfolge" @@ -14313,7 +14321,7 @@ msgid "Source" msgstr "Quelle" #: ../lib/modules/sambaSamAccount.inc:328 -#: ../lib/modules/sambaSamAccount.inc:1222 +#: ../lib/modules/sambaSamAccount.inc:1269 msgid "Special user" msgstr "Spezieller Benutzer" @@ -14350,7 +14358,7 @@ msgstr "" "Legt fest ob LAM Referrals automatisch folgen soll. Aktivieren Sie diese " "Funktion nur wenn Sie Referrals in Ihrem LDAP-Verzeichnis verwenden." -#: ../help/help.inc:342 +#: ../help/help.inc:348 msgid "" "Specifies if password mails may be sent to mail addresses other than the " "user's LDAP mail address." @@ -14358,7 +14366,7 @@ msgstr "" "Legt fest ob Passwortmails auch an EMail-Adressen gesendet werden dürfen die " "für diesen Benutzer nicht im LDAP-Verzeichnis stehen." -#: ../help/help.inc:338 +#: ../help/help.inc:344 msgid "Specifies if the mail should be sent as text or HTML." msgstr "Definiert, ob die email als Text oder HTML versendet wird." @@ -14387,7 +14395,7 @@ msgstr "" msgid "Specifies if this group has security possibilities." msgstr "Legt fest, dass diese Gruppe Sicherheitseinstellungen hat." -#: ../lib/modules/kopanoServer.inc:81 ../lib/modules/zarafaServer.inc:81 +#: ../lib/modules/kopanoServer.inc:80 ../lib/modules/zarafaServer.inc:81 msgid "" "Specifies if this server contains the public store. This may be changed only " "for new entries." @@ -14399,7 +14407,7 @@ msgstr "" msgid "Specifies if unknown clients are allowed." msgstr "Legt fest, ob unbekannte Clients erlaubt sind." -#: ../lib/modules/ppolicy.inc:133 +#: ../lib/modules/ppolicy.inc:132 msgid "Specifies if users are allowed to change their own passwords or not." msgstr "Legt fest, ob Benutzer ihre eigenen Passwörter ändern dürfen." @@ -14451,7 +14459,7 @@ msgstr "Bestimmt die maximale Größe der Datei in Bytes." msgid "Specifies the maximum lifetime of a ticket in days." msgstr "Bestimmt die maximale Gültigkeit eines Tickets in Tagen." -#: ../lib/modules/ppolicy.inc:117 +#: ../lib/modules/ppolicy.inc:116 msgid "" "Specifies the maximum number of seconds before a password is due to expire " "that expiration warning messages will be returned to a user." @@ -14471,7 +14479,7 @@ msgstr "" msgid "Specifies the maximum renewable lifetime of a ticket in days." msgstr "Bestimmt die maximale Laufzeitverlängerung eines Tickets in Tagen." -#: ../lib/modules/ppolicy.inc:121 +#: ../lib/modules/ppolicy.inc:120 msgid "" "Specifies the minimum number of characters that will be accepted in a " "password." @@ -14482,7 +14490,7 @@ msgid "Specifies the number of above password rules that must be fulfilled." msgstr "" "Legt die Anzahl der obigen Passwortregeln fest, die erfüllt sein müssen." -#: ../lib/modules/ppolicy.inc:109 +#: ../lib/modules/ppolicy.inc:108 msgid "" "Specifies the number of consecutive failed login attempts after which the " "password may not be used to login." @@ -14490,7 +14498,7 @@ msgstr "" "Dies ist die Anzahl von fehlerhaften Anmeldeversuchen in Folge nach der das " "Passwort gesperrt wird." -#: ../lib/modules/ppolicy.inc:93 +#: ../lib/modules/ppolicy.inc:92 msgid "" "Specifies the number of previously used passwords which should be saved in " "the password history. New passwords can only be set if they are not in the " @@ -14500,7 +14508,7 @@ msgstr "" "Passwörter können nur gesetzt werden wenn sie nicht in der Historie " "enthalten sind." -#: ../lib/modules/ppolicy.inc:113 +#: ../lib/modules/ppolicy.inc:112 msgid "" "Specifies the number of seconds after which old consecutive failed login " "attempts are purged from the failure counter, even though no successful " @@ -14509,7 +14517,7 @@ msgstr "" "Legt die Zeit in Sekunden fest, nach der fehlerhafte Anmeldeversuche " "gelöscht werden, auch wenn noch kein erfolgreicher Login stattgefunden hat." -#: ../lib/modules/ppolicy.inc:105 +#: ../lib/modules/ppolicy.inc:104 msgid "" "Specifies the number of seconds during which the password cannot be used to " "login due to too many consecutive failed bind attempts." @@ -14576,7 +14584,7 @@ msgstr "" "Gibt an, ob der Benutzer Administrator ist. Systemadministratoren können " "zusätzlich Firmen erstellen, ändern und löschen." -#: ../lib/modules/ppolicy.inc:137 +#: ../lib/modules/ppolicy.inc:136 msgid "" "Specifies whether the user's existing password must be sent along with their " "new password when changing a password. Attention, LAM does not support " @@ -14600,14 +14608,14 @@ msgstr "Startzeit" #: ../lib/modules/windowsUser.inc:189 ../lib/modules/windowsUser.inc:484 #: ../lib/modules/windowsUser.inc:853 ../lib/modules/windowsUser.inc:947 -#: ../lib/modules/windowsUser.inc:1137 ../lib/modules/windowsUser.inc:2727 -#: ../lib/modules/windowsUser.inc:2934 ../lib/modules/inetOrgPerson.inc:163 +#: ../lib/modules/windowsUser.inc:1136 ../lib/modules/windowsUser.inc:2699 +#: ../lib/modules/windowsUser.inc:2906 ../lib/modules/inetOrgPerson.inc:163 #: ../lib/modules/inetOrgPerson.inc:453 ../lib/modules/inetOrgPerson.inc:495 #: ../lib/modules/inetOrgPerson.inc:721 ../lib/modules/inetOrgPerson.inc:725 #: ../lib/modules/inetOrgPerson.inc:1281 ../lib/modules/inetOrgPerson.inc:1284 -#: ../lib/modules/inetOrgPerson.inc:1968 ../lib/modules/inetOrgPerson.inc:2018 -#: ../lib/modules/inetOrgPerson.inc:2724 ../lib/modules/inetOrgPerson.inc:3863 -#: ../lib/modules/inetOrgPerson.inc:3905 +#: ../lib/modules/inetOrgPerson.inc:1969 ../lib/modules/inetOrgPerson.inc:2019 +#: ../lib/modules/inetOrgPerson.inc:2725 ../lib/modules/inetOrgPerson.inc:3864 +#: ../lib/modules/inetOrgPerson.inc:3906 msgid "State" msgstr "Bundesland" @@ -14635,14 +14643,14 @@ msgstr "Schritt %s von %s" msgid "Steve" msgstr "Hans" -#: ../lib/modules/sambaSamAccount.inc:429 ../lib/modules/posixAccount.inc:2459 +#: ../lib/modules/sambaSamAccount.inc:429 ../lib/modules/posixAccount.inc:2417 #: ../lib/modules/windowsUser.inc:438 ../lib/modules/windowsUser.inc:444 -#: ../lib/modules/pykotaUser.inc:204 ../lib/modules/inetOrgPerson.inc:2175 +#: ../lib/modules/pykotaUser.inc:204 ../lib/modules/inetOrgPerson.inc:2176 #: ../lib/modules/asteriskVoicemail.inc:173 msgid "Steve Miller" msgstr "Hans Müller" -#: ../lib/modules/posixAccount.inc:2467 +#: ../lib/modules/posixAccount.inc:2425 msgid "Steve Miller,Room 2.14,123-123-1234,123-123-1234" msgstr "Hans Müller,Raum 2.14,123-123-1234,123-123-1234" @@ -14660,15 +14668,15 @@ msgstr "" #: ../lib/types/user.inc:115 ../lib/modules/windowsUser.inc:193 #: ../lib/modules/windowsUser.inc:460 ../lib/modules/windowsUser.inc:854 -#: ../lib/modules/windowsUser.inc:946 ../lib/modules/windowsUser.inc:1133 -#: ../lib/modules/windowsUser.inc:2728 ../lib/modules/windowsUser.inc:2933 +#: ../lib/modules/windowsUser.inc:946 ../lib/modules/windowsUser.inc:1132 +#: ../lib/modules/windowsUser.inc:2700 ../lib/modules/windowsUser.inc:2905 #: ../lib/modules/inetOrgPerson.inc:86 ../lib/modules/inetOrgPerson.inc:160 #: ../lib/modules/inetOrgPerson.inc:307 ../lib/modules/inetOrgPerson.inc:483 #: ../lib/modules/inetOrgPerson.inc:598 ../lib/modules/inetOrgPerson.inc:602 #: ../lib/modules/inetOrgPerson.inc:1245 ../lib/modules/inetOrgPerson.inc:1248 -#: ../lib/modules/inetOrgPerson.inc:1950 ../lib/modules/inetOrgPerson.inc:2006 -#: ../lib/modules/inetOrgPerson.inc:2647 ../lib/modules/inetOrgPerson.inc:3859 -#: ../lib/modules/inetOrgPerson.inc:3902 +#: ../lib/modules/inetOrgPerson.inc:1951 ../lib/modules/inetOrgPerson.inc:2007 +#: ../lib/modules/inetOrgPerson.inc:2648 ../lib/modules/inetOrgPerson.inc:3860 +#: ../lib/modules/inetOrgPerson.inc:3903 msgid "Street" msgstr "Straße" @@ -14682,20 +14690,20 @@ msgstr "Name der Struktur" msgid "Sub (entire subtree)" msgstr "Unterhalb (kompletter Baum unter Basis-DN)" -#: ../lib/types/netgroup.inc:96 ../lib/modules/nisnetgroup.inc:94 -#: ../lib/modules/nisnetgroup.inc:129 ../lib/modules/nisnetgroup.inc:144 -#: ../lib/modules/nisnetgroup.inc:216 ../lib/modules/nisnetgroup.inc:396 -#: ../lib/modules/nisnetgroup.inc:588 +#: ../lib/types/netgroup.inc:96 ../lib/modules/nisnetgroup.inc:93 +#: ../lib/modules/nisnetgroup.inc:128 ../lib/modules/nisnetgroup.inc:143 +#: ../lib/modules/nisnetgroup.inc:218 ../lib/modules/nisnetgroup.inc:409 +#: ../lib/modules/nisnetgroup.inc:572 msgid "Subgroups" msgstr "Untergruppen" -#: ../templates/config/confmain.php:359 ../lib/passwordExpirationJob.inc:87 +#: ../templates/config/confmain.php:363 ../lib/passwordExpirationJob.inc:87 #: ../lib/modules/selfRegistration.inc:93 #: ../lib/modules/selfRegistration.inc:218 #: ../lib/modules/passwordSelfReset.inc:123 -#: ../lib/modules/passwordSelfReset.inc:666 -#: ../lib/modules/passwordSelfReset.inc:682 ../help/help.inc:330 -#: ../help/help.inc:380 +#: ../lib/modules/passwordSelfReset.inc:664 +#: ../lib/modules/passwordSelfReset.inc:680 ../help/help.inc:336 +#: ../help/help.inc:386 msgid "Subject" msgstr "Betreff" @@ -14758,16 +14766,16 @@ msgstr "" "Sudo-Regeln mit höherer Zahl werden verwendet wenn mehrere Regeln passen." #: ../lib/modules/nisMailAliasUser.inc:92 -#: ../lib/modules/nisMailAliasUser.inc:365 ../lib/modules.inc:1069 +#: ../lib/modules/nisMailAliasUser.inc:357 ../lib/modules.inc:1068 msgid "Suffix" msgstr "Suffix" -#: ../lib/modules/posixGroup.inc:505 ../lib/modules/posixGroup.inc:634 +#: ../lib/modules/posixGroup.inc:454 ../lib/modules/posixGroup.inc:583 msgid "Suffix for GID/group name check" msgstr "Suffix für GID/Gruppennamenprüfung" -#: ../lib/modules/posixAccount.inc:372 ../lib/modules/posixAccount.inc:2246 -#: ../lib/modules/posixAccount.inc:2317 +#: ../lib/modules/posixAccount.inc:377 ../lib/modules/posixAccount.inc:2204 +#: ../lib/modules/posixAccount.inc:2275 msgid "Suffix for UID/user name check" msgstr "Suffix für UID/Benutzernamenprüfung" @@ -14776,11 +14784,11 @@ msgstr "Suffix für UID/Benutzernamenprüfung" msgid "Suffix for new users" msgstr "Suffix für neue Benutzer" -#: ../lib/modules/sambaSamAccount.inc:1337 +#: ../lib/modules/sambaSamAccount.inc:1386 msgid "Sunday" msgstr "Sonntag" -#: ../templates/selfService/adminMain.php:665 +#: ../templates/selfService/adminMain.php:677 msgid "Switch back to default label." msgstr "Zum Standardbezeichner zurückkehren." @@ -14810,7 +14818,7 @@ msgid "Sync MIT Kerberos password with Unix password" msgstr "MIT-Kerberos-Passwort mit Unix synchronisieren" #: ../lib/modules/passwordSelfReset.inc:151 -#: ../lib/modules/passwordSelfReset.inc:654 +#: ../lib/modules/passwordSelfReset.inc:652 msgid "Sync Samba 3 password" msgstr "Samba-3-Passwort synchronisieren" @@ -14826,15 +14834,15 @@ msgstr "Samba NT-Passwörter mit Unix synchronisieren" msgid "Sync Unix password with Windows password" msgstr "Unix-Passwort mit Windows synchronisieren" -#: ../lib/modules/windowsUser.inc:1838 +#: ../lib/modules/windowsUser.inc:1821 msgid "Sync Unix to Windows" msgstr "Unix nach Windows synchronisieren" -#: ../lib/modules/posixAccount.inc:1890 +#: ../lib/modules/posixAccount.inc:1866 msgid "Sync Unix to group of names" msgstr "Unix nach Namensgruppen synchronisieren" -#: ../lib/modules/posixAccount.inc:1904 +#: ../lib/modules/posixAccount.inc:1880 msgid "Sync Windows to Unix" msgstr "Windows nach Unix synchronisieren" @@ -14842,21 +14850,21 @@ msgstr "Windows nach Unix synchronisieren" msgid "Sync fields with page layout" msgstr "Felder mit Seitenlayout synchronisieren" -#: ../lib/modules/posixGroup.inc:378 ../lib/modules/posixGroup.inc:390 +#: ../lib/modules/posixGroup.inc:331 ../lib/modules/posixGroup.inc:340 #, php-format msgid "Sync from %s" msgstr "Aus %s synchronisieren" -#: ../lib/modules/posixAccount.inc:1894 +#: ../lib/modules/posixAccount.inc:1870 msgid "Sync group of names to Unix" msgstr "Namensgruppen nach Unix synchronisieren" -#: ../lib/modules/windowsUser.inc:1843 +#: ../lib/modules/windowsUser.inc:1826 msgid "Sync group of names to Windows" msgstr "Namensgruppen nach Windows synchronisieren" -#: ../lib/modules/posixAccount.inc:1883 ../lib/modules/posixAccount.inc:2261 -#: ../lib/modules/windowsUser.inc:1832 ../lib/modules/windowsUser.inc:3665 +#: ../lib/modules/posixAccount.inc:1861 ../lib/modules/posixAccount.inc:2219 +#: ../lib/modules/windowsUser.inc:1817 ../lib/modules/windowsUser.inc:3637 msgid "Sync groups" msgstr "Gruppen synchronisieren" @@ -14874,7 +14882,7 @@ msgid "Syntaxes" msgstr "Syntax" #: ../lib/modules/zarafaUser.inc:633 ../lib/modules/zarafaUser.inc:1300 -#: ../lib/modules/kopanoUser.inc:633 ../lib/modules/kopanoUser.inc:1300 +#: ../lib/modules/kopanoUser.inc:595 ../lib/modules/kopanoUser.inc:1219 msgid "System administrator" msgstr "Systemadministrator" @@ -14898,7 +14906,7 @@ msgid "TXT records" msgstr "TXT-Einträge" #: ../lib/passwordExpirationJob.inc:496 ../lib/passwordExpirationJob.inc:530 -#: ../help/help.inc:410 +#: ../help/help.inc:416 msgid "Target DN" msgstr "Ziel-DN" @@ -14925,17 +14933,17 @@ msgid "Technical name" msgstr "Technischer Name" #: ../templates/lists/changePassword.php:287 ../lib/types/user.inc:116 -#: ../lib/types/user.inc:219 ../lib/modules/windowsUser.inc:197 +#: ../lib/types/user.inc:220 ../lib/modules/windowsUser.inc:197 #: ../lib/modules/windowsUser.inc:508 ../lib/modules/windowsUser.inc:855 #: ../lib/modules/windowsUser.inc:943 ../lib/modules/windowsUser.inc:998 -#: ../lib/modules/windowsUser.inc:1146 ../lib/modules/windowsUser.inc:2729 -#: ../lib/modules/windowsUser.inc:2927 ../lib/modules/inetOrgPerson.inc:72 +#: ../lib/modules/windowsUser.inc:1145 ../lib/modules/windowsUser.inc:2701 +#: ../lib/modules/windowsUser.inc:2899 ../lib/modules/inetOrgPerson.inc:72 #: ../lib/modules/inetOrgPerson.inc:159 ../lib/modules/inetOrgPerson.inc:347 #: ../lib/modules/inetOrgPerson.inc:510 ../lib/modules/inetOrgPerson.inc:630 #: ../lib/modules/inetOrgPerson.inc:634 ../lib/modules/inetOrgPerson.inc:1387 -#: ../lib/modules/inetOrgPerson.inc:1390 ../lib/modules/inetOrgPerson.inc:1957 -#: ../lib/modules/inetOrgPerson.inc:2033 ../lib/modules/inetOrgPerson.inc:2592 -#: ../lib/modules/inetOrgPerson.inc:3868 ../lib/modules/inetOrgPerson.inc:3908 +#: ../lib/modules/inetOrgPerson.inc:1390 ../lib/modules/inetOrgPerson.inc:1958 +#: ../lib/modules/inetOrgPerson.inc:2034 ../lib/modules/inetOrgPerson.inc:2593 +#: ../lib/modules/inetOrgPerson.inc:3869 ../lib/modules/inetOrgPerson.inc:3909 msgid "Telephone number" msgstr "Telefonnummer" @@ -14943,7 +14951,7 @@ msgstr "Telefonnummer" msgid "Temp" msgstr "Aushilfskraft" -#: ../lib/modules/windowsUser.inc:456 ../lib/modules/pykotaUser.inc:755 +#: ../lib/modules/windowsUser.inc:456 ../lib/modules/pykotaUser.inc:737 #: ../lib/modules/inetOrgPerson.inc:261 msgid "Temp, contract till December" msgstr "Aushilfskraft, Vertrag bis Dezember" @@ -14958,8 +14966,8 @@ msgid "Templates" msgstr "Vorlagen" #: ../lib/modules/sambaSamAccount.inc:392 -#: ../lib/modules/sambaSamAccount.inc:1234 -#: ../lib/modules/sambaSamAccount.inc:1910 +#: ../lib/modules/sambaSamAccount.inc:1284 +#: ../lib/modules/sambaSamAccount.inc:1957 msgid "Terminal server options" msgstr "Terminalserver-Einstellungen" @@ -14971,16 +14979,16 @@ msgstr "Einstellungen testen" msgid "Tests" msgstr "Tests" -#: ../templates/config/confmain.php:374 ../lib/passwordExpirationJob.inc:89 +#: ../templates/config/confmain.php:378 ../lib/passwordExpirationJob.inc:89 #: ../lib/modules/selfRegistration.inc:68 #: ../lib/modules/selfRegistration.inc:223 ../lib/modules/bindDLZ.inc:172 #: ../lib/modules/bindDLZ.inc:176 ../lib/modules/bindDLZ.inc:442 #: ../lib/modules/bindDLZ.inc:1564 ../lib/modules/bindDLZ.inc:1936 #: ../lib/modules/passwordSelfReset.inc:127 #: ../lib/modules/passwordSelfReset.inc:137 -#: ../lib/modules/passwordSelfReset.inc:668 -#: ../lib/modules/passwordSelfReset.inc:684 ../help/help.inc:332 -#: ../help/help.inc:384 +#: ../lib/modules/passwordSelfReset.inc:666 +#: ../lib/modules/passwordSelfReset.inc:682 ../help/help.inc:338 +#: ../help/help.inc:390 msgid "Text" msgstr "Text" @@ -15038,7 +15046,7 @@ msgstr "" "Dies sind die DNs der Verzeichniseinträge, die die organisatorischen " "Einheiten der Person repräsentieren." -#: ../lib/modules/posixGroup.inc:502 +#: ../lib/modules/posixGroup.inc:451 msgid "" "The ID of this group was changed. You can update all user and host entries " "to the new group ID." @@ -15129,7 +15137,7 @@ msgstr "Der PC-Name darf nur A-Z, a-z und 0-9 enthalten." msgid "The PC name needs to be at least 2 characters long." msgstr "Der PC-Name muss mindestens zwei Zeichen lang sein." -#: ../help/help.inc:257 +#: ../help/help.inc:259 msgid "" "The PDF structure defines what information is exported as PDF file and how " "the pages are structured. You can manage the PDF structures in the PDF " @@ -15149,7 +15157,7 @@ msgstr "Datenfeld für RDN ist leer." msgid "The Radius realm of this account." msgstr "Der Radius-Bereich für dieses Konto." -#: ../lib/modules/sambaDomain.inc:82 +#: ../lib/modules/sambaDomain.inc:81 msgid "The SID of your Samba server. Get it with \"net getlocalsid\"." msgstr "Die Domänen-SID des Samba-Servers. Auszulesen mit \"net getlocalsid\"." @@ -15168,10 +15176,18 @@ msgstr "" msgid "The URL for the external password check is invalid." msgstr "Die URL für die externe Passwortprüfung ist ungültig." -#: ../lib/modules/kopanoServer.inc:77 ../lib/modules/zarafaServer.inc:77 +#: ../lib/modules/kopanoServer.inc:76 ../lib/modules/zarafaServer.inc:77 msgid "The Unix socket or named pipe to the server." msgstr "Das Unixsocket oder die Named Pipe zum Server." +#: ../lib/modules/yubiKeyUser.inc:89 ../lib/modules/yubiKeyUser.inc:105 +msgid "" +"The YubiKey id are the first 12 letters of the key output. Simlply touch " +"your YubiKey in an empty field to set it." +msgstr "" +"Die YubiKey-ID besteht aus den ersten 12 Ziffern der Ausgabe des Schlüssels. " +"Berühren Sie einfach Ihren YubiKey in einem leeren Eingabefeld." + #: ../lib/modules/asteriskVoicemail.inc:214 #: ../lib/modules/asteriskVoicemail.inc:215 msgid "The account context is invalid." @@ -15192,7 +15208,7 @@ msgstr "Der Accounttyp ist ungültig." msgid "The account will be locked after this date." msgstr "Der Account wird nach diesem Datum gesperrt." -#: ../help/help.inc:243 +#: ../help/help.inc:243 ../help/help.inc:251 msgid "The account will be saved under this LDAP suffix." msgstr "Der Account wird unter diesem LDAP-Suffix gespeichert." @@ -15216,7 +15232,7 @@ msgstr "Das legt den verfügbaren Speicherplatz für den Benutzer fest." msgid "The answer needs to be at least %s characters long." msgstr "Die Antwort muss mindestens %s Zeichen lang sein." -#: ../lib/modules/passwordSelfReset.inc:1471 +#: ../lib/modules/passwordSelfReset.inc:1469 msgid "The answer to the security question is wrong." msgstr "Die Antwort zur Sicherheitsfrage ist falsch." @@ -15229,7 +15245,7 @@ msgstr "" "Das Attribut %s wird von Ihrem LDAP-Server nicht für die Objektklasse %s " "unterstützt." -#: ../help/help.inc:327 +#: ../help/help.inc:331 msgid "" "The attribute (e.g. \"uid\") that contains the user name for the 2-factor " "service." @@ -15253,7 +15269,7 @@ msgstr "Die Anrufer-ID ist ungültig." #: ../templates/selfService/selfServiceLogin.php:116 #: ../lib/modules/selfRegistration.inc:529 -#: ../lib/modules/passwordSelfReset.inc:1141 +#: ../lib/modules/passwordSelfReset.inc:1139 msgid "The captcha was wrong." msgstr "Das Captcha war falsch." @@ -15265,8 +15281,8 @@ msgstr "Die Klassennamen enthalten ungültige Werte.." msgid "The class names may only contain ASCII characters." msgstr "Der Klassenname darf nur ASCII-Zeichen enthalten." -#: ../templates/config/confmain.php:161 -#: ../templates/selfService/adminMain.php:369 +#: ../templates/config/confmain.php:165 +#: ../templates/selfService/adminMain.php:375 msgid "The config file is not writable." msgstr "Die Konfigurationsdatei ist nicht schreibbar." @@ -15286,7 +15302,7 @@ msgstr "Der Standard-Gateway ist ungültig." msgid "The default interpretation of .qmail files." msgstr "Die Standardinterpretation von .qmail Dateien." -#: ../lib/modules/autoDelete.inc:226 ../lib/modules/autoDelete.inc:253 +#: ../lib/modules/autoDelete.inc:236 ../lib/modules/autoDelete.inc:263 msgid "The deletion date cannot be in the past." msgstr "Das Löschdatum kann nicht in der Vergangenheit liegen." @@ -15355,7 +15371,7 @@ msgstr "Der Name des Eintrags." msgid "The environment name may only contain ASCII characters." msgstr "Der Umgebungsname darf nur ASCII-Zeichen enthalten." -#: ../help/help.inc:387 +#: ../help/help.inc:393 msgid "" "The expiration date can be added with @@EXPIRE_DATE_DDMMYYYY@@ or " "@@EXPIRE_DATE_YYYYMMDD@@." @@ -15377,11 +15393,11 @@ msgstr "" msgid "The expiration time \"%s\" must be a number." msgstr "Die Ablaufzeit \"%s\" muss eine Zahl sein." -#: ../help/help.inc:411 +#: ../help/help.inc:417 msgid "The expired accounts will be moved to this DN." msgstr "Die abgelaufenen Konten werden zu dieser DN verschoben." -#: ../help/help.inc:362 +#: ../help/help.inc:368 msgid "The export will read entries of this DN." msgstr "Der Export umfasst Einträge dieser DN." @@ -15418,7 +15434,7 @@ msgstr "" "Die hochgeladene Datei ist zu groß. Bitte prüfen Sie den Parameter " "upload_max_size in der php.ini." -#: ../lib/modules/posixGroup.inc:524 +#: ../lib/modules/posixGroup.inc:473 msgid "" "The filter can be any regular expression, e.g. \".*\" = any characters, \"^" "\" = line start, \"$\" = line end." @@ -15426,7 +15442,7 @@ msgstr "" "Der Filter kann ein regulärer Ausdruck sein, z.B.: \".*\" = beliebige " "Zeichen, \"^\" = Zeilenanfang, \"$\" = Zeilenende." -#: ../lib/modules/nsview.inc:73 ../lib/modules/customFields.inc:198 +#: ../lib/modules/nsview.inc:72 ../lib/modules/customFields.inc:198 msgid "The filter can be any valid LDAP filter." msgstr "Der Filter kann ein beliebiger LDAP-Filter sein." @@ -15456,7 +15472,7 @@ msgstr "Folgende Suffixe fehlen in LDAP. LAM kann sie für Sie erstellen." msgid "The format of the logon hours field is invalid!" msgstr "Das Format der Anmeldezeiten ist ungültig!" -#: ../lib/modules/customScripts.inc:110 +#: ../lib/modules/customScripts.inc:109 msgid "The format of this custom script setting is invalid." msgstr "Das Format dieses Skriptes ist ungültig!" @@ -15478,7 +15494,7 @@ msgstr "Der Wert für den vollständigen Namen ist ungültig." msgid "The given user name matches multiple LDAP entries." msgstr "Der angegebene Benutzername passt auf mehrere LDAP-Einträge." -#: ../lib/modules/ppolicy.inc:101 +#: ../lib/modules/ppolicy.inc:100 msgid "" "The grace authentication limit specifies the number of times that an expired " "password may be used to login." @@ -15510,7 +15526,7 @@ msgstr "" msgid "The home directory will be connected under this drive letter." msgstr "Das Heimatverzeichnis wird unter diesem Buchstaben verbunden." -#: ../lib/modules/windowsHost.inc:87 +#: ../lib/modules/windowsHost.inc:86 msgid "The host is managed by this contact person." msgstr "Der Server wird von dieser Kontaktperson verwaltet." @@ -15535,7 +15551,7 @@ msgstr "Der Hostname ist ungültig." msgid "The initials of the user's first names." msgstr "Die Initialen der Vornamen des Benutzers." -#: ../help/help.inc:360 +#: ../help/help.inc:366 msgid "The input data must be formatted in LDIF format." msgstr "Die Eingabe muss LDIF-formatiert sein." @@ -15649,11 +15665,11 @@ msgstr "" "Der Text für die Bestätigungsmails muss den Rücksetzlink @@resetLink@@ " "enthalten." -#: ../help/help.inc:385 +#: ../help/help.inc:391 msgid "The mail text of all mails." msgstr "Der Text aller emails." -#: ../help/help.inc:333 +#: ../help/help.inc:339 msgid "The mail text of all password mails." msgstr "Der Text für alle Passwortmails." @@ -15737,7 +15753,7 @@ msgstr "Der Name der Erweiterung (z.B. voicemail oder sip)." msgid "The name of the subnet. Example: 192.168.10.0" msgstr "Der Name des Subnetzes. Beispiel: 192.168.10.0" -#: ../lib/modules/sambaDomain.inc:78 +#: ../lib/modules/sambaDomain.inc:77 msgid "The name of your Windows domain or workgroup." msgstr "Der Name ihrer Windows-Domäne oder Arbeitsgruppe." @@ -15766,7 +15782,7 @@ msgstr "" msgid "The net mask is invalid." msgstr "Die Netzmaske ist ungültig." -#: ../lib/modules.inc:1262 +#: ../lib/modules.inc:1263 msgid "" "The new password will be stored in the directory after you save this account." msgstr "" @@ -15807,7 +15823,7 @@ msgstr "Die Objektklasse %s wird von Ihrem LDAP-Server nicht unterstützt." msgid "The office name of the user (e.g. YourCompany, Human Resources)." msgstr "Der Büroname des Benutzers (z.B. IhreFirma, Personalabteilung)." -#: ../templates/selfService/selfServiceMain.php:372 ../lib/modules.inc:1929 +#: ../templates/selfService/selfServiceMain.php:373 ../lib/modules.inc:1935 msgid "The operation was stopped because of the above errors." msgstr "Die Operation wurde aufgrund obiger Fehler gestoppt." @@ -15837,7 +15853,7 @@ msgstr "Der Parameter @@password@@ wird durch das neue Passwort ersetzt." msgid "The parameter @@principal@@ will be replaced with the principal name." msgstr "Der Parameter @@principal@@ wird durch den Benutzernamen ersetzt." -#: ../templates/config/confsave.php:74 ../templates/config/confmain.php:96 +#: ../templates/config/confsave.php:74 ../templates/config/confmain.php:100 #: ../templates/config/mainlogin.php:63 #: ../templates/selfService/adminLogin.php:52 msgid "The password is invalid! Please try again." @@ -15931,7 +15947,7 @@ msgstr "" "Dies ist das Passwort Ihres IMAP-Administrators. Der Benutzername für den " "Administrator wird im LAM-Serverprofil gespeichert." -#: ../templates/lists/changePassword.php:647 ../lib/modules.inc:1205 +#: ../templates/lists/changePassword.php:647 ../lib/modules.inc:1206 msgid "The password was set to:" msgstr "Das Passwort wurde auf folgenden Wert gesetzt:" @@ -15971,7 +15987,7 @@ msgstr "Dies ist der Preis pro Seite für einen Druckjob." msgid "The price for each print job." msgstr "Dies ist der Preis pro Druckjob." -#: ../lib/modules/posixAccount.inc:345 +#: ../lib/modules/posixAccount.inc:350 msgid "" "The primary group for this account. You can insert a GID number or a group " "name." @@ -15979,11 +15995,11 @@ msgstr "" "Die primäre Gruppe dieses Accounts. Sie können eine GID-Nummer oder einen " "Gruppennamen angeben." -#: ../lib/modules/posixAccount.inc:457 +#: ../lib/modules/posixAccount.inc:462 msgid "The primary group the host should be member of." msgstr "Die primäre Gruppe des Hosts." -#: ../lib/modules/posixAccount.inc:419 +#: ../lib/modules/posixAccount.inc:424 msgid "The primary group the user should be member of." msgstr "Die primäre Gruppe des Benutzers." @@ -16030,7 +16046,7 @@ msgstr "Die Raumnummer des Mitarbeiterbüros." msgid "The scope in which to search" msgstr "Der Suchbereich für diese Suche" -#: ../lib/modules/customScripts.inc:64 ../lib/modules/customScripts.inc:77 +#: ../lib/modules/customScripts.inc:63 ../lib/modules/customScripts.inc:76 msgid "" "The scripts will be run on your web server in the user context of your web " "server (e.g. apache/www-data)." @@ -16038,10 +16054,10 @@ msgstr "" "Die Skripte werden auf Ihrem Webserver gestartet. Dazu wird der " "Benutzerkontext des Webservers (z.B. apache/www-data) verwendet." -#: ../lib/modules/kopanoServer.inc:89 ../lib/modules/nisMailAliasUser.inc:105 +#: ../lib/modules/kopanoServer.inc:88 ../lib/modules/nisMailAliasUser.inc:105 #: ../lib/modules/asteriskAccount.inc:243 ../lib/modules/qmailUser.inc:162 #: ../lib/modules/zarafaGroup.inc:116 ../lib/modules/kopanoGroup.inc:115 -#: ../lib/modules/sambaSamAccount.inc:402 ../lib/modules/posixAccount.inc:320 +#: ../lib/modules/sambaSamAccount.inc:402 ../lib/modules/posixAccount.inc:325 #: ../lib/modules/zarafaUser.inc:151 ../lib/modules/windowsUser.inc:256 #: ../lib/modules/inetOrgPerson.inc:750 ../lib/modules/freeRadius.inc:128 #: ../lib/modules/kopanoUser.inc:151 ../lib/modules/zarafaServer.inc:89 @@ -16096,11 +16112,11 @@ msgstr "Der Straßenname der Benutzeradresse." msgid "The structural object class of the entries." msgstr "Die strukturelle Objektklasse der Einträge." -#: ../help/help.inc:381 +#: ../help/help.inc:387 msgid "The subject of all mails." msgstr "Der Betreff der emails." -#: ../help/help.inc:331 +#: ../help/help.inc:337 msgid "The subject of all password mails." msgstr "Der Betreff aller Passwortmails." @@ -16210,7 +16226,7 @@ msgstr "Die hochgeladene Datei hat nicht die erlaubte Dateiendung (%s)." msgid "The uploaded file is too large (> %s bytes)." msgstr "Die hochgeladene Datei ist zu groß (> %s Bytes)." -#: ../lib/modules/ldapPublicKey.inc:131 +#: ../lib/modules/ldapPublicKey.inc:130 msgid "The uploaded file seems to be no valid public key." msgstr "" "Die hochgeladene Datei scheint kein gültiger öffentlicher Schlüssel zu sein." @@ -16319,7 +16335,7 @@ msgstr "Es wurden keine Attribute als RDN-Attribut markiert." msgid "The wildcard for the account creation link is @@creationLink@@." msgstr "Der Platzhalter für den Erstellungslink ist @@creationLink@@." -#: ../lib/modules/passwordSelfReset.inc:130 ../help/help.inc:336 +#: ../lib/modules/passwordSelfReset.inc:130 ../help/help.inc:342 msgid "The wildcard for the new password is @@newPassword@@." msgstr "Der Platzhalter für das neue Passwort ist @@newPassword@@." @@ -16332,11 +16348,11 @@ msgstr "Der Platzhalter für den Resetlink ist @@resetLink@@." msgid "There are %s members in group %s:" msgstr "Es sind %s Mitglieder in der Gruppe %s:" -#: ../lib/modules/posixGroup.inc:765 ../lib/modules/sambaGroupMapping.inc:579 +#: ../lib/modules/posixGroup.inc:714 ../lib/modules/sambaGroupMapping.inc:576 msgid "There are still users who have this group as their primary group." msgstr "Es gibt noch Benutzer, deren primäre Gruppe diese Gruppe ist." -#: ../lib/modules/sambaGroupMapping.inc:576 +#: ../lib/modules/sambaGroupMapping.inc:573 msgid "There can be only one group of this type." msgstr "Es kann nur eine Gruppe diesen Typs geben." @@ -16357,7 +16373,7 @@ msgstr "Beim Upload traten Fehler auf:" msgid "There will be %s updates done with this mass update" msgstr "Es werden %s Änderungen mit dieser Massenänderung durchgeführt" -#: ../lib/modules/posixGroup.inc:490 +#: ../lib/modules/posixGroup.inc:439 msgid "" "These are the minimum and maximum numbers to use for group IDs when creating " "new group accounts. New group accounts will always get the highest number in " @@ -16367,7 +16383,7 @@ msgstr "" "Gruppen erstellt werden. Neue Gruppen bekommen die immer die höchste " "verwendete Zahl plus 1." -#: ../lib/modules/posixAccount.inc:332 +#: ../lib/modules/posixAccount.inc:337 msgid "" "These are the minimum and maximum numbers to use for machine IDs when " "creating new accounts for hosts. The range should be different from that of " @@ -16377,7 +16393,7 @@ msgstr "" "erstellt werden. Der Bereich sollte sich von dem der Benutzer unterscheiden. " "Neue Hosts bekommen die immer die höchste verwendete Zahl plus 1." -#: ../lib/modules/posixAccount.inc:328 +#: ../lib/modules/posixAccount.inc:333 msgid "" "These are the minimum and maximum numbers to use for user IDs when creating " "new user accounts. The range should be different from that of machines. New " @@ -16400,7 +16416,7 @@ msgstr "" "Diese Klassen werden mittels Autovervollständigung beim Hinzufügen neuer " "Klassen vorgeschlagen." -#: ../lib/modules/nisnetgroup.inc:99 ../lib/modules/nisnetgroup.inc:103 +#: ../lib/modules/nisnetgroup.inc:98 ../lib/modules/nisnetgroup.inc:102 msgid "" "These entries specify the members of the netgroup. You can limit the set to " "a host name, a user name, a domain name or any combination of them." @@ -16441,7 +16457,7 @@ msgstr "" "Diese GID-Nummer ist ungültig! Bitte geben Sie eine Nummer ein oder einen " "Gruppennamen." -#: ../help/help.inc:289 +#: ../help/help.inc:291 msgid "" "This HTML code will be placed on top of all self service pages. E.g. you can " "use this to place your custom logo. Any HTML code is permitted." @@ -16524,11 +16540,11 @@ msgstr "" "Üblicherweise sollte er ähnlich zu \"/usr/sbin/kadmin -k -t /home/www-data/" "apache.keytab -p realm/changepwd\" sein." -#: ../lib/modules/customScripts.inc:111 +#: ../lib/modules/customScripts.inc:110 msgid "This custom script setting includes an invalid account type." msgstr "Dieses Skripteinstellung enthält einen ungültigen Accounttyp." -#: ../lib/modules/customScripts.inc:113 ../lib/modules/customScripts.inc:115 +#: ../lib/modules/customScripts.inc:112 ../lib/modules/customScripts.inc:114 msgid "This custom script setting includes an invalid action type." msgstr "Diese Skripteinstellung enthält einen ungültigen Aktionstyp." @@ -16575,27 +16591,27 @@ msgstr "Dies ist der Ort des Benutzers." msgid "This document was automatically created by LDAP Account Manager" msgstr "Dieses Dokument wurde von LDAP Account Manager erstellt." -#: ../help/help.inc:399 +#: ../help/help.inc:405 msgid "This email address will be set as BCC address of all mails." msgstr "Diese EMailadresse wird als BCC Adresse für alle emails verwendet." -#: ../help/help.inc:395 +#: ../help/help.inc:401 msgid "This email address will be set as CC address of all mails." msgstr "Diese EMailadresse wird als CC Adresse für alle emails verwendet." -#: ../help/help.inc:377 +#: ../help/help.inc:383 msgid "This email address will be set as reply-to address of all mails." msgstr "" "Diese EMailadresse wird als Antwort-An Adresse für alle emails verwendet." -#: ../help/help.inc:340 +#: ../help/help.inc:346 msgid "" "This email address will be set as reply-to address of all password mails." msgstr "" "Diese EMailadresse wird als Antwort-An Adresse für alle Passwortmails " "verwendet." -#: ../help/help.inc:373 +#: ../help/help.inc:379 msgid "" "This email address will be set as sender address of all mails. If empty the " "system default (php.ini) will be used." @@ -16603,7 +16619,7 @@ msgstr "" "Diese Adresse wird als Absenderadresse für die emails verwendet. Falls leer " "dann wird die Systemeinstellung (php.ini) verwendet." -#: ../help/help.inc:329 +#: ../help/help.inc:335 msgid "" "This email address will be set as sender address of all password mails. If " "empty the system default (php.ini) will be used." @@ -16640,9 +16656,9 @@ msgstr "Dieser Eintrag enthält einen Unterbaum mit %s Einträgen." #: ../templates/lib/141_jquery-validationEngine-lang.php:49 #: ../templates/tools/importexport.php:395 #: ../lib/modules/selfRegistration.inc:559 -#: ../lib/modules/kopanoAddressList.inc:154 ../lib/modules/nsview.inc:107 -#: ../lib/modules/nsview.inc:108 ../lib/modules/nsview.inc:109 -#: ../lib/modules/nsview.inc:110 ../lib/modules/customFields.inc:2759 +#: ../lib/modules/kopanoAddressList.inc:154 ../lib/modules/nsview.inc:106 +#: ../lib/modules/nsview.inc:107 ../lib/modules/nsview.inc:108 +#: ../lib/modules/nsview.inc:109 ../lib/modules/customFields.inc:2759 #: ../lib/modules/customFields.inc:4751 msgid "This field is required." msgstr "Dieses Feld ist erforderlich." @@ -16786,11 +16802,11 @@ msgstr "" "wenn wenn Sie Meldungen über überschrittene Größenbeschränkungen bekommen." #: ../lib/modules/courierMailAlias.inc:94 ../lib/modules/nisObject.inc:74 -#: ../lib/modules/automount.inc:72 ../lib/modules/oracleService.inc:70 +#: ../lib/modules/automount.inc:72 ../lib/modules/oracleService.inc:69 msgid "This is an optional description for this entry." msgstr "Hier können Sie eine optionale Beschreibung für den Eintrag angeben." -#: ../help/help.inc:277 +#: ../help/help.inc:279 msgid "" "This is needed to find the LDAP DNs of your user accounts. E.g. if you use " "\"uid\" and your user inputs \"miller\" then LAM will search for an account " @@ -16802,7 +16818,7 @@ msgstr "" #: ../lib/passwordExpirationJob.inc:530 #: ../lib/modules/zarafaDynamicGroup.inc:183 ../lib/modules/ipHost.inc:125 -#: ../lib/modules/posixGroup.inc:766 ../lib/modules/posixGroup.inc:767 +#: ../lib/modules/posixGroup.inc:715 ../lib/modules/posixGroup.inc:716 #: ../lib/modules/device.inc:128 ../lib/modules/eduPerson.inc:245 #: ../lib/modules/eduPerson.inc:247 ../lib/modules/zarafaAddressList.inc:155 #: ../lib/modules/posixAccount.inc:121 ../lib/modules/posixAccount.inc:122 @@ -16819,7 +16835,7 @@ msgstr "Das ist keine korrekte DN!" msgid "This is not a valid RID number!" msgstr "Das ist keine korrekte RID-Nummer!" -#: ../lib/modules/sambaGroupMapping.inc:578 +#: ../lib/modules/sambaGroupMapping.inc:575 msgid "This is not a valid Samba 3 group type!" msgstr "Das ist kein korrekter Samba 3 Gruppentyp!" @@ -16979,11 +16995,11 @@ msgstr "Dies ist die EMailadresse des Aliases." msgid "This is the group name which will be shown in Windows." msgstr "Das ist der Gruppenname, der in Windows angezeigt wird." -#: ../lib/modules/windowsHost.inc:83 +#: ../lib/modules/windowsHost.inc:82 msgid "This is the host's location (e.g. Munich, server room 3)." msgstr "Das ist der Standort des Servers (z.B. München, Serverraum 3)." -#: ../help/help.inc:237 +#: ../help/help.inc:237 ../help/help.inc:251 msgid "" "This is the identifier for the relative DN value. It must be one of the " "given allowed LDAP attributes (e.g. user accounts usually use \"uid\" while " @@ -17027,7 +17043,7 @@ msgstr "" "Die Einträge können entweder vordefinierte Werte \"#Wert\" oder eigne Werte " "\"Wert:Beschreibung\" sein. Mehrere Werte werden durch Strichpunkte getrennt." -#: ../lib/modules/posixAccount.inc:377 +#: ../lib/modules/posixAccount.inc:382 msgid "This is the list of valid login shells." msgstr "Dies ist eine Liste von gültigen Login-Shells." @@ -17053,12 +17069,12 @@ msgstr "" "Hiermit legen Sie die Mindestlänge für Antworten auf die Sicherheitsfrage " "fest." -#: ../lib/modules/nisnetgroup.inc:87 ../lib/modules/organizationalRole.inc:90 +#: ../lib/modules/nisnetgroup.inc:86 ../lib/modules/organizationalRole.inc:90 #: ../lib/modules/groupOfNames.inc:89 msgid "This is the name of this group." msgstr "Das ist der Name dieser Gruppe." -#: ../lib/modules/posixAccount.inc:465 +#: ../lib/modules/posixAccount.inc:470 msgid "" "This is the natural name of the host. If empty, the host name will be used." msgstr "" @@ -17076,7 +17092,7 @@ msgstr "" "Dies ist der übliche Name des Benutzers, falls leer wird Vor- und Nachname " "verwendet." -#: ../lib/modules/posixAccount.inc:439 ../lib/modules/windowsUser.inc:126 +#: ../lib/modules/posixAccount.inc:444 ../lib/modules/windowsUser.inc:126 msgid "" "This is the natural name of the user. If empty, the first and last name or " "user name is used." @@ -17084,7 +17100,7 @@ msgstr "" "Dies ist der übliche Name des Benutzers, falls leer wird Vor- und Nachname " "oder der Benutzername verwendet." -#: ../lib/modules/sambaDomain.inc:122 +#: ../lib/modules/sambaDomain.inc:121 msgid "" "This is the number of bad logon attempts (0 - 999) before the account is " "deactivated. 0 means unlimited attempts." @@ -17092,7 +17108,7 @@ msgstr "" "Dies ist die Anzahl von fehlerhaften Anmeldeversuchen (0 - 999) bis zur " "Sperrung eines Accounts. 0 bedeutet unbegrenzt." -#: ../lib/modules/sambaDomain.inc:106 +#: ../lib/modules/sambaDomain.inc:105 msgid "" "This is the number of passwords which are saved to prevent that users reuse " "old passwords." @@ -17181,12 +17197,12 @@ msgstr "" msgid "This is the target email address for the user's mails." msgstr "Dies ist die Zieladresse für alle Mails des Benutzers." -#: ../lib/modules/customScripts.inc:53 ../lib/modules/uidObject.inc:59 +#: ../lib/modules/customScripts.inc:52 ../lib/modules/uidObject.inc:59 #: ../lib/modules/aliasEntry.inc:62 msgid "This is the target of this alias entry." msgstr "Das ist der Zieleintrag dieses Aliases." -#: ../lib/modules/sambaDomain.inc:134 +#: ../lib/modules/sambaDomain.inc:133 msgid "" "This is the time (in minutes) for which the user may not log in after the " "account was locked. -1 means forever." @@ -17244,13 +17260,13 @@ msgstr "" msgid "This is used to mark this account as resource." msgstr "Damit wird der Account als Ressource markiert." -#: ../lib/modules/qmailUser.inc:1260 ../lib/modules/windowsUser.inc:4121 -#: ../lib/modules/shadowAccount.inc:1054 ../lib/modules/freeRadius.inc:825 +#: ../lib/modules/qmailUser.inc:1100 ../lib/modules/windowsUser.inc:4093 +#: ../lib/modules/shadowAccount.inc:1081 ../lib/modules/freeRadius.inc:825 msgid "This job deletes or moves user accounts when they expire." msgstr "Dieser Job löscht oder verschiebt Benutzerkonten wenn diese ablaufen." -#: ../lib/modules/qmailUser.inc:1329 ../lib/modules/windowsUser.inc:4029 -#: ../lib/modules/shadowAccount.inc:959 ../lib/modules/freeRadius.inc:893 +#: ../lib/modules/qmailUser.inc:1169 ../lib/modules/windowsUser.inc:4001 +#: ../lib/modules/shadowAccount.inc:986 ../lib/modules/freeRadius.inc:893 msgid "" "This job sends out emails to inform your users that their account will " "expire soon." @@ -17274,7 +17290,7 @@ msgstr "Login-Shell ist ungültig!" msgid "This mailbox will be created/deleted." msgstr "Dieses Postfach wird erstellt/gelöscht." -#: ../lib/modules.inc:1331 +#: ../lib/modules.inc:1337 msgid "This may overwrite existing values with profile data. Continue?" msgstr "" "Damit überschreiben Sie evtl. existierende Daten mit denen aus dem Profil. " @@ -17329,11 +17345,15 @@ msgstr "Dieses Paar von Erweiterungsname und Priorität existiert bereits." msgid "This program is run after the login." msgstr "Dieses Programm wird nach der Anmeldung gestartet." +#: ../lib/2factor.inc:548 +msgid "This service requires a browser with \"WebAuthn\" support." +msgstr "Diese Service benötigt einen Browser mit \"WebAuthn\" Unterstützung." + #: ../lib/modules/sambaSamAccount.inc:390 msgid "This specifies the reconnect policy." msgstr "Hier spezifizieren Sie die Wiederherstellungsrichtlinie." -#: ../lib/modules/kopanoServer.inc:93 ../lib/modules/kopanoGroup.inc:119 +#: ../lib/modules/kopanoServer.inc:92 ../lib/modules/kopanoGroup.inc:119 #: ../lib/modules/kopanoContact.inc:110 #: ../lib/modules/kopanoDynamicGroup.inc:99 ../lib/modules/kopanoUser.inc:190 #: ../lib/modules/kopanoAddressList.inc:83 @@ -17369,15 +17389,15 @@ msgstr "" "Hier spezifizieren Sie, was passiert wenn die Client-Verbindung getrennt " "wird." -#: ../help/help.inc:323 +#: ../help/help.inc:327 msgid "This text is displayed as footer on the self service login page." msgstr "Dieser Text wird unten auf der SelfService-Loginseite angezeigt." -#: ../help/help.inc:325 +#: ../help/help.inc:329 msgid "This text is displayed as footer on the self service main page." msgstr "Dieser Text wird unten auf der SelfService-Hauptseite angezeigt." -#: ../help/help.inc:307 +#: ../help/help.inc:311 msgid "" "This text is displayed on top of the 2-factor page. You can also input HTML " "code here." @@ -17385,7 +17405,7 @@ msgstr "" "Dieser Text wird oben auf der 2-Faktor-Seite angezeigt. Sie können auch HTML-" "Code eingeben." -#: ../help/help.inc:281 +#: ../help/help.inc:283 msgid "" "This text is displayed on top of the self service login page. You can also " "input HTML code here." @@ -17393,7 +17413,7 @@ msgstr "" "Dieser Text wird oben auf der SelfService-Loginseite angezeigt. Sie können " "auch HTML-Code eingeben." -#: ../help/help.inc:283 +#: ../help/help.inc:285 msgid "" "This text is displayed on top of the self service main page. You can also " "input HTML code here." @@ -17401,7 +17421,7 @@ msgstr "" "Dieser Text wird oben auf der SelfService-Hauptseite angezeigt. Sie können " "auch HTML-Code eingeben." -#: ../help/help.inc:293 +#: ../help/help.inc:295 msgid "" "This text is placed as label for the password field on the login page. LAM " "will use \"Password\" if you do not enter any text." @@ -17409,7 +17429,7 @@ msgstr "" "Dieser Text wird als Beschreibung für das Passwortfeld auf der Loginseite " "angezeigt. LAM verwendet \"Passwort\" wenn Sie nichts eingeben." -#: ../help/help.inc:279 +#: ../help/help.inc:281 msgid "" "This text should shortly describe your selected LDAP search attribute (e.g. " "email or user name)." @@ -17453,7 +17473,7 @@ msgstr "Dieser Benutzer wurde nicht gefunden!" msgid "This value can only be \"Room\" or \"Equipment\"." msgstr "Dieser Wert darf nur \"Room\" oder \"Equipment\" sein." -#: ../lib/modules/kopanoServer.inc:156 +#: ../lib/modules/kopanoServer.inc:155 #: ../lib/modules/zarafaDynamicGroup.inc:188 #: ../lib/modules/zarafaDynamicGroup.inc:189 #: ../lib/modules/zarafaContact.inc:180 ../lib/modules/zarafaContact.inc:181 @@ -17488,22 +17508,22 @@ msgstr "Dieser Wert darf nur \"true\" oder \"false\" sein." msgid "This value can only be \"true\", \"false\" or \"system\"." msgstr "Dieser Wert darf nur \"true\", \"false\" oder \"system\" sein." -#: ../lib/modules/posixGroup.inc:764 +#: ../lib/modules/posixGroup.inc:713 msgid "This value must be a list of user names separated by semicolons." msgstr "" "Dieser Wert muss eine Liste von Benutzernamen, getrennt duch Kommata, sein." -#: ../help/help.inc:346 +#: ../help/help.inc:352 msgid "This will create a new organisational unit under the selected one." msgstr "Dies erzeugt eine neue Organisational Unit unterhalb der markierten." -#: ../lib/modules/posixAccount.inc:365 +#: ../lib/modules/posixAccount.inc:370 msgid "This will create the user's home directory on the specified server." msgstr "" "Wenn Sie dieses Kästchen aktivieren, dann wird das Heimatverzeichnis des " "Benutzers auf dem angegebenen Server erstellt." -#: ../help/help.inc:348 +#: ../help/help.inc:354 msgid "" "This will delete the selected organisational unit. The OU has to be empty." msgstr "Dies löscht die markierte Organisational Unit. Die OU muss leer sein." @@ -17512,7 +17532,7 @@ msgstr "Dies löscht die markierte Organisational Unit. Die OU muss leer sein." msgid "This will delete the selected profile." msgstr "Dies löscht das markierte Profil." -#: ../help/help.inc:303 +#: ../help/help.inc:307 msgid "" "This will disable the check of the SSL certificates for the 2-factor " "authentication service. Not recommended for production usage." @@ -17522,9 +17542,9 @@ msgstr "" #: ../lib/modules/kolabGroup.inc:109 ../lib/modules/puppetClient.inc:116 #: ../lib/modules/qmailUser.inc:230 ../lib/modules/zarafaContact.inc:91 -#: ../lib/modules/posixGroup.inc:532 ../lib/modules/eduPerson.inc:150 +#: ../lib/modules/posixGroup.inc:481 ../lib/modules/eduPerson.inc:150 #: ../lib/modules/pykotaGroup.inc:124 ../lib/modules/sambaSamAccount.inc:405 -#: ../lib/modules/posixAccount.inc:311 ../lib/modules/zarafaUser.inc:155 +#: ../lib/modules/posixAccount.inc:316 ../lib/modules/zarafaUser.inc:155 #: ../lib/modules/kopanoContact.inc:91 #: ../lib/modules/courierMailAccount.inc:133 ../lib/modules/pykotaUser.inc:164 #: ../lib/modules/shadowAccount.inc:199 ../lib/modules/ldapPublicKey.inc:98 @@ -17536,13 +17556,13 @@ msgstr "" "Damit können Sie die Erweiterung automatisch beim Laden des Profils " "aktivieren." -#: ../lib/modules/posixGroup.inc:536 +#: ../lib/modules/posixGroup.inc:485 msgid "This will force syncing with group of names members of the same group." msgstr "" "Damit wird die Synchronisierung mit Namensgruppenmitgliedern der gleichen " "Gruppe erzwungen." -#: ../lib/modules/customScripts.inc:93 +#: ../lib/modules/customScripts.inc:92 msgid "" "This will hide the custom scripts tab when you edit an account. Manual " "actions are not possible when the tab is not visible." @@ -17550,7 +17570,7 @@ msgstr "" "Damit verhindern Sie die Anzeige des Tabs \"Eigene Felder\" beim Editieren " "eines Eintrags. Manuelle Aktionen sind in diesem Fall nicht möglich." -#: ../lib/modules/posixAccount.inc:398 +#: ../lib/modules/posixAccount.inc:403 msgid "This will not add the posixAccount object class to the account." msgstr "" "Damit wird die posixAccount Objektklasse nicht zum Account hinzugefügt." @@ -17567,7 +17587,7 @@ msgstr "" "Hiermit setzen Sie ein Zufallspasswort, das auf dem Bildschirm angezeigt " "wird oder senden es dem Benutzer per mail." -#: ../help/help.inc:311 +#: ../help/help.inc:315 msgid "" "This will set a random password and display it on the screen or send it to " "the user via mail. Please edit your LAM server profile to setup the mail " @@ -17585,13 +17605,13 @@ msgstr "" "Damit setzen Sie den Kontostatus des Benutzers. Sie können das Konto damit " "deaktivieren." -#: ../lib/modules/sambaSamAccount.inc:1336 +#: ../lib/modules/sambaSamAccount.inc:1386 msgid "Thursday" msgstr "Donnerstag" #: ../lib/modules/mitKerberos.inc:135 ../lib/modules/mitKerberos.inc:194 #: ../lib/modules/mitKerberos.inc:229 ../lib/modules/mitKerberos.inc:253 -#: ../lib/modules/mitKerberos.inc:346 ../lib/modules/mitKerberos.inc:792 +#: ../lib/modules/mitKerberos.inc:346 ../lib/modules/mitKerberos.inc:814 #: ../lib/modules/heimdalKerberos.inc:109 #: ../lib/modules/heimdalKerberos.inc:163 #: ../lib/modules/heimdalKerberos.inc:198 @@ -17607,7 +17627,7 @@ msgstr "Ticketlaufzeit" msgid "Ticket lifetime must be a number." msgstr "Ticketlaufzeit muss eine Zahl sein." -#: ../templates/config/jobList.php:164 ../lib/modules/sambaSamAccount.inc:1358 +#: ../templates/config/jobList.php:164 ../lib/modules/sambaSamAccount.inc:1386 msgid "Time" msgstr "Zeit" @@ -17623,8 +17643,8 @@ msgstr "Dies ist der Zeitpunkt der letzten Anmeldung." msgid "Time of user's last password change." msgstr "Letzte Passwortänderung des Benutzers." -#: ../templates/config/confmain.php:272 -#: ../templates/selfService/adminMain.php:472 +#: ../templates/config/confmain.php:276 +#: ../templates/selfService/adminMain.php:478 #: ../lib/modules/sambaSamAccount.inc:350 ../help/help.inc:111 msgid "Time zone" msgstr "Zeitzone" @@ -17658,7 +17678,7 @@ msgstr "Zeitlimit" msgid "Timeouts" msgstr "Zeitlimits" -#: ../lib/modules/pykotaUser.inc:619 ../lib/modules/pykotaUser.inc:1013 +#: ../lib/modules/pykotaUser.inc:607 ../lib/modules/pykotaUser.inc:995 msgid "Title" msgstr "Titel" @@ -17666,11 +17686,11 @@ msgstr "Titel" msgid "To" msgstr "Mit" -#: ../lib/modules/posixAccount.inc:431 +#: ../lib/modules/posixAccount.inc:436 msgid "To disable login use /bin/false." msgstr "Verwenden Sie /bin/false um den Login zu deaktivieren." -#: ../templates/config/confmain.php:381 +#: ../templates/config/confmain.php:385 msgid "Tool settings" msgstr "Werkzeugeinstellungen" @@ -17695,8 +17715,8 @@ msgid "Total money paid by the user." msgstr "Vom Benutzer bezahlter Gesamtbetrag." #: ../lib/modules/pykotaUser.inc:143 ../lib/modules/pykotaUser.inc:241 -#: ../lib/modules/pykotaUser.inc:551 ../lib/modules/pykotaUser.inc:917 -#: ../lib/modules/pykotaUser.inc:978 +#: ../lib/modules/pykotaUser.inc:552 ../lib/modules/pykotaUser.inc:899 +#: ../lib/modules/pykotaUser.inc:960 msgid "Total paid" msgstr "Gesamtbetrag" @@ -17704,35 +17724,35 @@ msgstr "Gesamtbetrag" msgid "Total paid (read-only)" msgstr "Gesamtbetrag (nur lesen)" -#: ../lib/types/user.inc:822 +#: ../lib/types/user.inc:823 msgid "Translate GID number to group name" msgstr "GID-Nummer als Gruppenname darstellen" -#: ../templates/config/confmain.php:198 ../help/help.inc:81 +#: ../templates/config/confmain.php:202 ../help/help.inc:81 msgid "Tree suffix" msgstr "Baumansicht" -#: ../templates/tools/multiEdit.php:118 ../lib/adminHeader.inc:175 +#: ../templates/tools/multiEdit.php:120 ../lib/adminHeader.inc:175 msgid "Tree view" msgstr "Baumansicht" -#: ../templates/config/confmain.php:647 +#: ../templates/config/confmain.php:662 msgid "TreeSuffix is invalid!" msgstr "BaumSuffix ist ungültig!" -#: ../lib/modules/sambaSamAccount.inc:1336 +#: ../lib/modules/sambaSamAccount.inc:1386 msgid "Tuesday" msgstr "Dienstag" -#: ../templates/tools/multiEdit.php:142 +#: ../templates/tools/multiEdit.php:144 #: ../templates/selfService/profManage.php:191 #: ../templates/schema/schema.php:230 ../lib/env.inc:155 #: ../lib/modules/zarafaUser.inc:134 ../lib/modules/zarafaUser.inc:260 #: ../lib/modules/zarafaUser.inc:384 ../lib/modules/zarafaUser.inc:565 #: ../lib/modules/zarafaUser.inc:1292 ../lib/modules/zarafaUser.inc:1826 #: ../lib/modules/kopanoUser.inc:134 ../lib/modules/kopanoUser.inc:260 -#: ../lib/modules/kopanoUser.inc:384 ../lib/modules/kopanoUser.inc:565 -#: ../lib/modules/kopanoUser.inc:1292 ../lib/modules/kopanoUser.inc:1837 +#: ../lib/modules/kopanoUser.inc:384 ../lib/modules/kopanoUser.inc:534 +#: ../lib/modules/kopanoUser.inc:1211 ../lib/modules/kopanoUser.inc:1756 #: ../lib/modules/kolabSharedFolder.inc:136 #: ../lib/modules/kolabSharedFolder.inc:165 #: ../lib/modules/kolabSharedFolder.inc:204 @@ -17746,8 +17766,8 @@ msgstr "Typ" msgid "UID" msgstr "Benutzer ID" -#: ../lib/modules/posixAccount.inc:380 ../lib/modules/posixAccount.inc:2212 -#: ../lib/modules/posixAccount.inc:2283 +#: ../lib/modules/posixAccount.inc:385 ../lib/modules/posixAccount.inc:2170 +#: ../lib/modules/posixAccount.inc:2241 msgid "UID generator" msgstr "UID-Generator" @@ -17771,20 +17791,20 @@ msgstr "" #: ../lib/types/host.inc:99 ../lib/types/user.inc:119 #: ../lib/modules/qmailUser.inc:197 ../lib/modules/qmailUser.inc:289 -#: ../lib/modules/qmailUser.inc:372 ../lib/modules/qmailUser.inc:535 -#: ../lib/modules/qmailUser.inc:1093 ../lib/modules/qmailUser.inc:1218 +#: ../lib/modules/qmailUser.inc:372 ../lib/modules/qmailUser.inc:456 +#: ../lib/modules/qmailUser.inc:933 ../lib/modules/qmailUser.inc:1058 #: ../lib/modules/zarafaContact.inc:117 ../lib/modules/zarafaContact.inc:182 #: ../lib/modules/zarafaContact.inc:183 ../lib/modules/zarafaContact.inc:217 #: ../lib/modules/zarafaContact.inc:513 ../lib/modules/zarafaContact.inc:623 #: ../lib/modules/zarafaContact.inc:634 ../lib/modules/posixAccount.inc:98 -#: ../lib/modules/posixAccount.inc:213 ../lib/modules/posixAccount.inc:275 -#: ../lib/modules/posixAccount.inc:296 ../lib/modules/posixAccount.inc:327 -#: ../lib/modules/posixAccount.inc:331 ../lib/modules/posixAccount.inc:340 -#: ../lib/modules/posixAccount.inc:1638 ../lib/modules/posixAccount.inc:2148 +#: ../lib/modules/posixAccount.inc:218 ../lib/modules/posixAccount.inc:280 +#: ../lib/modules/posixAccount.inc:301 ../lib/modules/posixAccount.inc:332 +#: ../lib/modules/posixAccount.inc:336 ../lib/modules/posixAccount.inc:345 +#: ../lib/modules/posixAccount.inc:1619 ../lib/modules/posixAccount.inc:2106 #: ../lib/modules/kopanoContact.inc:117 ../lib/modules/kopanoContact.inc:182 #: ../lib/modules/kopanoContact.inc:183 ../lib/modules/kopanoContact.inc:217 -#: ../lib/modules/kopanoContact.inc:466 ../lib/modules/kopanoContact.inc:578 -#: ../lib/modules/kopanoContact.inc:589 +#: ../lib/modules/kopanoContact.inc:463 ../lib/modules/kopanoContact.inc:575 +#: ../lib/modules/kopanoContact.inc:586 msgid "UID number" msgstr "UID Nummer" @@ -17834,10 +17854,16 @@ msgstr "" msgid "URL" msgstr "URL" -#: ../help/help.inc:301 +#: ../help/help.inc:303 msgid "URL of external 2-factor authentication service." msgstr "URL des externen 2-Faktor-Authentifizierungsdienstes." +#: ../help/help.inc:305 +msgid "URLs of external 2-factor authentication service. Enter one per line." +msgstr "" +"URLs des externen 2-Faktor-Authentifizierungsdienstes. Geben Sie eine URL " +"pro Zeile an." + #: ../lib/modules/bindDLZ.inc:2079 ../lib/modules/bindDLZ.inc:2088 #: ../lib/modules/bindDLZ.inc:2128 ../lib/modules/bindDLZ.inc:2859 msgid "Unable to add DNS record." @@ -17848,17 +17874,17 @@ msgid "Unable to change ACL on IMAP server for mailbox deletion." msgstr "" "Die ACLs für die Löschung des Postfaches konnten nicht geändert werden." -#: ../lib/modules/mitKerberos.inc:1160 ../lib/modules/mitKerberos.inc:1164 +#: ../lib/modules/mitKerberos.inc:1182 ../lib/modules/mitKerberos.inc:1186 #: ../lib/modules/heimdalKerberos.inc:1031 msgid "Unable to change Kerberos password." msgstr "Konnte Kerberos-Passwort nicht ändern." -#: ../lib/modules/windowsUser.inc:3081 ../lib/modules/windowsUser.inc:3129 -#: ../lib/modules/windowsUser.inc:3155 +#: ../lib/modules/windowsUser.inc:3053 ../lib/modules/windowsUser.inc:3101 +#: ../lib/modules/windowsUser.inc:3127 msgid "Unable to change password." msgstr "Konnte Passwort nicht ändern." -#: ../templates/selfService/selfServiceMain.php:379 +#: ../templates/selfService/selfServiceMain.php:380 msgid "" "Unable to change your account. Maybe you do not have enough rights to change " "the settings." @@ -17866,7 +17892,7 @@ msgstr "" "Ihr Konto konnte nicht verändert werden. Vielleicht fehlen Ihnen die nötigen " "Rechte zur Änderung?" -#: ../templates/selfService/selfServiceMain.php:376 +#: ../templates/selfService/selfServiceMain.php:377 msgid "" "Unable to change your account. Your changes might violate the password " "policy." @@ -17878,7 +17904,7 @@ msgstr "" msgid "Unable to connect to database." msgstr "Kann keine Verbindung zur Datenbank herstellen." -#: ../lib/remote.inc:80 +#: ../lib/remote.inc:73 ../lib/remote.inc:84 msgid "Unable to connect to remote server!" msgstr "Kann keine Verbindung zum Server herstellen!" @@ -17904,7 +17930,7 @@ msgstr "Kann neue OU nicht erstellen!" msgid "Unable to create new automount map." msgstr "Kann neue Automount-Map nicht erstellen." -#: ../templates/selfService/profManage.php:61 ../lib/config.inc:209 +#: ../templates/selfService/profManage.php:61 ../lib/config.inc:222 msgid "Unable to create new profile!" msgstr "Kann das Profil nicht erstellen!" @@ -17945,8 +17971,8 @@ msgstr "Das Postfach konnte nicht gelöscht werden." #: ../templates/config/profmanage.php:129 #: ../templates/selfService/profManage.php:99 -#: ../templates/profedit/profilemain.php:89 ../lib/config.inc:222 -#: ../lib/config.inc:233 ../lib/config.inc:239 ../lib/config.inc:247 +#: ../templates/profedit/profilemain.php:89 ../lib/config.inc:235 +#: ../lib/config.inc:246 ../lib/config.inc:252 ../lib/config.inc:260 msgid "Unable to delete profile!" msgstr "Kann das Profil nicht löschen!" @@ -17955,18 +17981,18 @@ msgstr "Kann das Profil nicht löschen!" msgid "Unable to find a printer with name \"%s\"." msgstr "Konnte keinen Drucker namens \"%s\" finden." -#: ../lib/modules/posixAccount.inc:2606 ../lib/modules/posixAccount.inc:2615 -#: ../lib/modules/posixAccount.inc:2917 ../lib/modules/nisNetGroupUser.inc:512 -#: ../lib/modules/nisnetgroup.inc:557 ../lib/modules/groupOfNamesUser.inc:390 +#: ../lib/modules/posixAccount.inc:2564 ../lib/modules/posixAccount.inc:2573 +#: ../lib/modules/posixAccount.inc:2875 ../lib/modules/nisNetGroupUser.inc:512 +#: ../lib/modules/nisnetgroup.inc:541 ../lib/modules/groupOfNamesUser.inc:390 msgid "Unable to find group in LDAP." msgstr "Kann Gruppe nicht im LDAP-Baum finden." -#: ../lib/modules/passwordSelfReset.inc:1180 +#: ../lib/modules/passwordSelfReset.inc:1178 msgid "Unable to find password security answer for this account." msgstr "Konnte keine Sicherheitsantwort für diesen Account finden." -#: ../lib/modules/passwordSelfReset.inc:1164 -#: ../lib/modules/passwordSelfReset.inc:1414 +#: ../lib/modules/passwordSelfReset.inc:1162 +#: ../lib/modules/passwordSelfReset.inc:1412 msgid "Unable to find password security question for this account." msgstr "Konnte keine Sicherheitsfrage für diesen Account finden." @@ -17978,8 +18004,8 @@ msgstr "Kann Rolle nicht im LDAP-Baum finden." msgid "Unable to find the user name in LDAP." msgstr "Kann den Benutzernamen nicht in LDAP finden." -#: ../lib/modules/passwordSelfReset.inc:1151 -#: ../lib/modules/passwordSelfReset.inc:1503 +#: ../lib/modules/passwordSelfReset.inc:1149 +#: ../lib/modules/passwordSelfReset.inc:1501 msgid "Unable to find user account." msgstr "Benutzer wurde nicht gefunden." @@ -17989,11 +18015,11 @@ msgstr "" "Konnte Serverzertifikat nicht importieren. Bitte nutzen Sie die Funktion zum " "manuellen Hochladen." -#: ../lib/modules.inc:1627 ../lib/modules.inc:1631 +#: ../lib/modules.inc:1633 ../lib/modules.inc:1637 msgid "Unable to load LDAP entry:" msgstr "Kann den LDAP-Eintrag nicht einlesen:" -#: ../lib/remote.inc:170 +#: ../lib/remote.inc:174 #, php-format msgid "Unable to load key %s." msgstr "Kann Schlüssel %s nicht laden." @@ -18007,7 +18033,7 @@ msgstr "Kann das Profil nicht laden!" msgid "Unable to locate mailbox on IMAP." msgstr "Das Postfach wurde nicht gefunden." -#: ../lib/remote.inc:126 +#: ../lib/remote.inc:130 msgid "Unable to login to remote server!" msgstr "Kann keine Verbindung zum Server herstellen!" @@ -18015,7 +18041,7 @@ msgstr "Kann keine Verbindung zum Server herstellen!" msgid "Unable to process this file." msgstr "Kann diese Datei nicht verarbeiten." -#: ../lib/remote.inc:162 +#: ../lib/remote.inc:166 #, php-format msgid "Unable to read %s." msgstr "Kann %s nicht lesen." @@ -18036,10 +18062,10 @@ msgstr "" "Leider konnte Ihr neuer Benutzer nicht erstellt werden. Bitte versuchen Sie " "es erneut." -#: ../lib/modules/passwordSelfReset.inc:1713 -#: ../lib/modules/passwordSelfReset.inc:1723 -#: ../lib/modules/passwordSelfReset.inc:1729 -#: ../lib/modules/passwordSelfReset.inc:1738 +#: ../lib/modules/passwordSelfReset.inc:1711 +#: ../lib/modules/passwordSelfReset.inc:1721 +#: ../lib/modules/passwordSelfReset.inc:1727 +#: ../lib/modules/passwordSelfReset.inc:1736 msgid "Unable to reset password." msgstr "Konnte Passwort nicht zurücksetzen." @@ -18053,23 +18079,23 @@ msgstr "Konnte Bild nicht auslesen." msgid "Unable to retrieve schema!" msgstr "Konnte Schema nicht auslesen!" -#: ../templates/selfService/adminMain.php:280 +#: ../templates/selfService/adminMain.php:286 #: ../templates/profedit/profilepage.php:135 msgid "Unable to save profile!" msgstr "Kann das Profil nicht speichern!" -#: ../lib/account.inc:1238 ../lib/account.inc:1276 +#: ../lib/account.inc:1253 ../lib/account.inc:1291 msgid "Unable to send mail!" msgstr "Kann die mail nicht versenden!" -#: ../lib/modules/autoDelete.inc:362 +#: ../lib/modules/autoDelete.inc:372 #, php-format msgid "Unable to set deletion date on %s." msgstr "Konnte Löschdatum für \"%s\" nicht setzen." -#: ../lib/modules/posixAccount.inc:893 ../lib/modules/posixAccount.inc:3003 -#: ../lib/modules/posixAccount.inc:3386 ../lib/modules/inetOrgPerson.inc:959 -#: ../lib/modules/inetOrgPerson.inc:2457 +#: ../lib/modules/posixAccount.inc:880 ../lib/modules/posixAccount.inc:2962 +#: ../lib/modules/posixAccount.inc:3345 ../lib/modules/inetOrgPerson.inc:959 +#: ../lib/modules/inetOrgPerson.inc:2458 msgid "Unable to set password" msgstr "Konnte Passwort nicht setzen." @@ -18092,8 +18118,8 @@ msgstr "Konnte DNS-Eintrag \"%s\" nicht aktualisieren." msgid "Unable to upload logo file." msgstr "Konnte Logodatei nicht hochladen." -#: ../lib/modules/passwordSelfReset.inc:1238 -#: ../lib/modules/passwordSelfReset.inc:1522 +#: ../lib/modules/passwordSelfReset.inc:1236 +#: ../lib/modules/passwordSelfReset.inc:1520 msgid "Unable to verify your password reset request. Please try again." msgstr "" "Leider konnte Ihre Passwortrücksetzung nicht verifiziert werden. Bitte " @@ -18114,18 +18140,18 @@ msgid "Universal" msgstr "Universell" #: ../templates/lists/changePassword.php:406 -#: ../templates/lists/changePassword.php:443 ../lib/types/user.inc:302 -#: ../lib/types/user.inc:456 ../lib/types/user.inc:495 -#: ../lib/types/user.inc:1086 ../lib/modules/posixGroup.inc:430 +#: ../templates/lists/changePassword.php:443 ../lib/types/user.inc:303 +#: ../lib/types/user.inc:457 ../lib/types/user.inc:496 +#: ../lib/types/user.inc:1087 ../lib/modules/posixGroup.inc:379 #: ../lib/modules/posixAccount.inc:159 msgid "Unix" msgstr "Unix" -#: ../templates/tests/lamdaemonTest.php:217 +#: ../templates/tests/lamdaemonTest.php:218 msgid "Unix account" msgstr "Unix-Account" -#: ../lib/modules/posixAccount.inc:1795 ../lib/modules/posixAccount.inc:2260 +#: ../lib/modules/posixAccount.inc:1773 ../lib/modules/posixAccount.inc:2218 msgid "Unix groups" msgstr "Unix-Gruppen" @@ -18149,8 +18175,8 @@ msgstr "Unbekannte Vollmacht: %s" msgid "Unkown change type" msgstr "Unbekannter Änderungstyp" -#: ../lib/types/user.inc:437 ../lib/types/user.inc:442 -#: ../lib/types/user.inc:523 +#: ../lib/types/user.inc:438 ../lib/types/user.inc:443 +#: ../lib/types/user.inc:524 msgid "Unlock" msgstr "Entsperren" @@ -18165,12 +18191,12 @@ msgstr "Account entsperren" msgid "Unlock account?" msgstr "Account entsperren?" -#: ../lib/modules/posixGroup.inc:242 ../lib/modules/posixAccount.inc:1709 +#: ../lib/modules/posixGroup.inc:225 ../lib/modules/posixAccount.inc:1690 #: ../lib/modules/inetOrgPerson.inc:1568 msgid "Unlock password" msgstr "Passwort reaktivieren" -#: ../lib/types/user.inc:889 +#: ../lib/types/user.inc:890 msgid "Unlocked" msgstr "Entsperrt" @@ -18179,7 +18205,7 @@ msgstr "Entsperrt" msgid "Unsolved dependency:" msgstr "Ungelöste Abhängigkeit:" -#: ../templates/misc/ajax.php:257 ../templates/config/conftypes.php:201 +#: ../templates/misc/ajax.php:288 ../templates/config/conftypes.php:201 #: ../templates/pdfedit/pdfpage.php:266 ../templates/pdfedit/pdfpage.php:330 msgid "Up" msgstr "Hoch" @@ -18212,8 +18238,8 @@ msgid "Updating object" msgstr "Ändere Eintrag" #: ../templates/config/mainmanage.php:354 ../templates/pdfedit/pdfmain.php:293 -#: ../lib/modules/windowsUser.inc:2000 ../lib/modules/ldapPublicKey.inc:149 -#: ../lib/modules/inetOrgPerson.inc:1695 ../lib/modules/inetOrgPerson.inc:1869 +#: ../lib/modules/windowsUser.inc:1978 ../lib/modules/ldapPublicKey.inc:148 +#: ../lib/modules/inetOrgPerson.inc:1696 ../lib/modules/inetOrgPerson.inc:1870 msgid "Upload" msgstr "Hochladen" @@ -18231,7 +18257,7 @@ msgstr "" msgid "Upload accounts to LDAP" msgstr "Accounts in LDAP anlegen" -#: ../lib/modules/ldapPublicKey.inc:145 ../lib/modules/customFields.inc:177 +#: ../lib/modules/ldapPublicKey.inc:144 ../lib/modules/customFields.inc:177 #: ../lib/modules/customFields.inc:4381 msgid "Upload file" msgstr "Datei hochladen" @@ -18240,11 +18266,11 @@ msgstr "Datei hochladen" msgid "Upload file and create accounts" msgstr "Datei hochladen und Accounts erstellen" -#: ../lib/upload.inc:218 ../lib/lists.inc:1252 +#: ../lib/upload.inc:218 ../lib/lists.inc:1251 msgid "Upload has finished" msgstr "Upload wurde beendet" -#: ../lib/modules/windowsUser.inc:1995 ../lib/modules/inetOrgPerson.inc:1691 +#: ../lib/modules/windowsUser.inc:1974 ../lib/modules/inetOrgPerson.inc:1692 msgid "Upload image" msgstr "Bild hochladen" @@ -18283,19 +18309,19 @@ msgstr "Unix-Passwort verwenden" #: ../lib/modules/selfRegistration.inc:109 #: ../lib/modules/selfRegistration.inc:228 #: ../lib/modules/passwordSelfReset.inc:175 -#: ../lib/modules/passwordSelfReset.inc:687 +#: ../lib/modules/passwordSelfReset.inc:685 msgid "Use captcha" msgstr "Captcha verwenden" -#: ../templates/selfService/adminMain.php:436 ../help/help.inc:296 +#: ../templates/selfService/adminMain.php:442 ../help/help.inc:298 msgid "Use for all operations" msgstr "Für alle Operationen verwenden" #: ../lib/modules/sambaSamAccount.inc:275 #: ../lib/modules/sambaSamAccount.inc:278 #: ../lib/modules/sambaSamAccount.inc:447 -#: ../lib/modules/sambaSamAccount.inc:1094 -#: ../lib/modules/sambaSamAccount.inc:1701 +#: ../lib/modules/sambaSamAccount.inc:1126 +#: ../lib/modules/sambaSamAccount.inc:1748 msgid "Use no password" msgstr "Kein Passwort setzen" @@ -18307,7 +18333,7 @@ msgstr "" "Hier können Sie weitere Emailadressen im Format \"smtp:benutzer@domain.de\" " "angeben." -#: ../help/help.inc:354 +#: ../help/help.inc:360 msgid "" "Use this to enter an additional LDAP filter (e.g. \"(cn!=admin)\") to reduce " "the number of entries to modify." @@ -18323,7 +18349,7 @@ msgstr "" "Mit dem zusätzlichen LDAP-Filter (z.B. \"(cn!=admin)\") können Sie die " "Anzahl sichtbarer Einträge für diesen Accounttyp reduzieren." -#: ../help/help.inc:295 +#: ../help/help.inc:297 msgid "" "Use this to enter an additional LDAP filter (e.g. " "\"(objectClass=passwordSelfReset)\") to reduce the number of accounts who " @@ -18345,7 +18371,7 @@ msgid "Use this to hide this entry from the address book." msgstr "" "Benutzen Sie diese Einstellung um Einträge im Adressbuch zu verstecken." -#: ../help/help.inc:305 +#: ../help/help.inc:309 msgid "" "Use this to overwrite the default label for the 2-factor input field. " "Default is \"PIN+Token\"." @@ -18375,11 +18401,11 @@ msgstr "" "Benutzen Sie Platzhalter wie $uid$ für LDAP-Attribute des aktuellen LAM-" "Administrators." -#: ../lib/modules/pykotaBillingCode.inc:86 +#: ../lib/modules/pykotaBillingCode.inc:85 msgid "Used balance for the billing code." msgstr "Verbrauchter Betrag für den Rechnungscode." -#: ../lib/modules/quota.inc:113 ../lib/modules/quota.inc:486 +#: ../lib/modules/quota.inc:113 ../lib/modules/quota.inc:467 msgid "Used blocks" msgstr "Benutzte Blöcke" @@ -18395,7 +18421,7 @@ msgstr "Benutzt von Attributen" msgid "Used by object classes" msgstr "Benutzt von Objektklassen" -#: ../lib/modules/sambaDomain.inc:98 +#: ../lib/modules/sambaDomain.inc:97 msgid "Used for calculating RIDs from UID/GID. Do not change if unsure." msgstr "" "Wird für die Berechnung der RIDs aus UID/GID verwendet. Nicht ändern, wenn " @@ -18405,7 +18431,7 @@ msgstr "" msgid "Used for registration context." msgstr "Wird für den Registrierungskontext verwendet." -#: ../lib/modules/quota.inc:135 ../lib/modules/quota.inc:490 +#: ../lib/modules/quota.inc:135 ../lib/modules/quota.inc:468 msgid "Used inodes" msgstr "benutzte Inodes" @@ -18433,8 +18459,8 @@ msgstr "" "Damit wird in regelmäßigen Intervallen geprüft, ob das Gerät noch " "angeschlossen ist." -#: ../lib/types/sudo.inc:81 ../lib/modules/nisnetgroup.inc:227 -#: ../lib/modules/nisnetgroup.inc:593 ../lib/modules/nisMailAlias.inc:349 +#: ../lib/types/sudo.inc:81 ../lib/modules/nisnetgroup.inc:226 +#: ../lib/modules/nisnetgroup.inc:577 ../lib/modules/nisMailAlias.inc:355 msgid "User" msgstr "Benutzer" @@ -18446,23 +18472,23 @@ msgstr "Benutzeraccounts (z.B. Unix, Samba und Kolab)" #: ../lib/modules/asteriskAccount.inc:268 #: ../lib/modules/asteriskAccount.inc:338 #: ../lib/modules/asteriskAccount.inc:596 -#: ../lib/modules/asteriskAccount.inc:985 -#: ../lib/modules/asteriskAccount.inc:1229 +#: ../lib/modules/asteriskAccount.inc:986 +#: ../lib/modules/asteriskAccount.inc:1230 msgid "User agent" msgstr "Benutzeragent" #: ../lib/modules/sambaSamAccount.inc:299 -#: ../lib/modules/sambaSamAccount.inc:1120 +#: ../lib/modules/sambaSamAccount.inc:1154 msgid "User can change password" msgstr "Benutzer kann Passwort ändern" #: ../lib/modules/inetOrgPerson.inc:165 ../lib/modules/inetOrgPerson.inc:786 -#: ../lib/modules/inetOrgPerson.inc:1518 ../lib/modules/inetOrgPerson.inc:2831 -#: ../lib/modules/inetOrgPerson.inc:3885 +#: ../lib/modules/inetOrgPerson.inc:1518 ../lib/modules/inetOrgPerson.inc:2832 +#: ../lib/modules/inetOrgPerson.inc:3886 msgid "User certificates" msgstr "Benutzerzertifikate" -#: ../lib/types/user.inc:672 +#: ../lib/types/user.inc:673 #, php-format msgid "User count: %s" msgstr "Anzahl Benutzer: %s" @@ -18471,7 +18497,7 @@ msgstr "Anzahl Benutzer: %s" msgid "User description." msgstr "Benutzerbeschreibung" -#: ../lib/modules/posixAccount.inc:415 +#: ../lib/modules/posixAccount.inc:420 msgid "User description. If left empty first and last name will be used." msgstr "Benutzerbeschreibung, falls leer wird Vor- und Nachname verwendet." @@ -18488,9 +18514,9 @@ msgid "User modification" msgstr "Vom Benutzer änderbar" #: ../lib/modules/mitKerberos.inc:223 ../lib/modules/mitKerberos.inc:249 -#: ../lib/modules/mitKerberos.inc:365 ../lib/modules/mitKerberos.inc:631 -#: ../lib/modules/mitKerberos.inc:796 ../lib/modules/sambaSamAccount.inc:301 -#: ../lib/modules/sambaSamAccount.inc:1126 ../lib/modules/windowsUser.inc:1216 +#: ../lib/modules/mitKerberos.inc:365 ../lib/modules/mitKerberos.inc:636 +#: ../lib/modules/mitKerberos.inc:818 ../lib/modules/sambaSamAccount.inc:301 +#: ../lib/modules/sambaSamAccount.inc:1161 ../lib/modules/windowsUser.inc:1216 #: ../lib/modules/heimdalKerberos.inc:105 #: ../lib/modules/heimdalKerberos.inc:192 #: ../lib/modules/heimdalKerberos.inc:219 @@ -18501,11 +18527,11 @@ msgid "User must change password" msgstr "Benutzer muss Passwort ändern" #: ../templates/lists/changePassword.php:273 ../templates/login.php:313 -#: ../templates/config/confmain.php:281 ../lib/types/alias.inc:94 +#: ../templates/config/confmain.php:285 ../lib/types/alias.inc:94 #: ../lib/types/user.inc:118 ../lib/types/user.inc:120 #: ../lib/selfService.inc:488 ../lib/modules/mitKerberos.inc:115 #: ../lib/modules/mitKerberos.inc:210 ../lib/modules/mitKerberos.inc:248 -#: ../lib/modules/mitKerberos.inc:334 ../lib/modules/mitKerberos.inc:783 +#: ../lib/modules/mitKerberos.inc:334 ../lib/modules/mitKerberos.inc:805 #: ../lib/modules/selfRegistration.inc:392 #: ../lib/modules/selfRegistration.inc:486 #: ../lib/modules/selfRegistration.inc:493 ../lib/modules/account.inc:94 @@ -18520,25 +18546,25 @@ msgstr "Benutzer muss Passwort ändern" #: ../lib/modules/nisNetGroupHost.inc:217 #: ../lib/modules/nisNetGroupHost.inc:260 ../lib/modules/posixAccount.inc:103 #: ../lib/modules/posixAccount.inc:104 ../lib/modules/posixAccount.inc:107 -#: ../lib/modules/posixAccount.inc:205 ../lib/modules/posixAccount.inc:293 -#: ../lib/modules/posixAccount.inc:410 ../lib/modules/posixAccount.inc:1625 -#: ../lib/modules/posixAccount.inc:2132 ../lib/modules/windowsUser.inc:129 +#: ../lib/modules/posixAccount.inc:210 ../lib/modules/posixAccount.inc:298 +#: ../lib/modules/posixAccount.inc:415 ../lib/modules/posixAccount.inc:1604 +#: ../lib/modules/posixAccount.inc:2090 ../lib/modules/windowsUser.inc:129 #: ../lib/modules/windowsUser.inc:410 ../lib/modules/windowsUser.inc:839 -#: ../lib/modules/windowsUser.inc:982 ../lib/modules/windowsUser.inc:1114 -#: ../lib/modules/windowsUser.inc:2712 ../lib/modules/pykotaUser.inc:115 +#: ../lib/modules/windowsUser.inc:982 ../lib/modules/windowsUser.inc:1113 +#: ../lib/modules/windowsUser.inc:2684 ../lib/modules/pykotaUser.inc:115 #: ../lib/modules/pykotaUser.inc:274 ../lib/modules/pykotaUser.inc:276 -#: ../lib/modules/pykotaUser.inc:304 ../lib/modules/pykotaUser.inc:735 -#: ../lib/modules/pykotaUser.inc:887 ../lib/modules/pykotaUser.inc:905 -#: ../lib/modules/nisnetgroup.inc:155 ../lib/modules/nisnetgroup.inc:496 +#: ../lib/modules/pykotaUser.inc:304 ../lib/modules/pykotaUser.inc:717 +#: ../lib/modules/pykotaUser.inc:869 ../lib/modules/pykotaUser.inc:887 +#: ../lib/modules/nisnetgroup.inc:154 ../lib/modules/nisnetgroup.inc:483 #: ../lib/modules/inetOrgPerson.inc:100 ../lib/modules/inetOrgPerson.inc:166 #: ../lib/modules/inetOrgPerson.inc:685 ../lib/modules/inetOrgPerson.inc:1186 -#: ../lib/modules/inetOrgPerson.inc:1189 ../lib/modules/inetOrgPerson.inc:1931 -#: ../lib/modules/inetOrgPerson.inc:1966 ../lib/modules/inetOrgPerson.inc:2181 -#: ../lib/modules/inetOrgPerson.inc:2927 ../lib/modules/inetOrgPerson.inc:3891 +#: ../lib/modules/inetOrgPerson.inc:1189 ../lib/modules/inetOrgPerson.inc:1932 +#: ../lib/modules/inetOrgPerson.inc:1967 ../lib/modules/inetOrgPerson.inc:2182 +#: ../lib/modules/inetOrgPerson.inc:2928 ../lib/modules/inetOrgPerson.inc:3892 #: ../lib/modules/inetOrgPerson.inc:3921 -#: ../lib/modules/passwordSelfReset.inc:629 -#: ../lib/modules/passwordSelfReset.inc:1024 -#: ../lib/modules/passwordSelfReset.inc:1255 +#: ../lib/modules/passwordSelfReset.inc:627 +#: ../lib/modules/passwordSelfReset.inc:1022 +#: ../lib/modules/passwordSelfReset.inc:1253 #: ../lib/modules/heimdalKerberos.inc:93 ../lib/modules/heimdalKerberos.inc:179 #: ../lib/modules/heimdalKerberos.inc:217 #: ../lib/modules/heimdalKerberos.inc:285 @@ -18548,8 +18574,8 @@ msgstr "Benutzername" #: ../lib/modules/windowsUser.inc:137 ../lib/modules/windowsUser.inc:657 #: ../lib/modules/windowsUser.inc:877 ../lib/modules/windowsUser.inc:990 -#: ../lib/modules/windowsUser.inc:1123 ../lib/modules/windowsUser.inc:2714 -#: ../lib/modules/windowsUser.inc:3635 +#: ../lib/modules/windowsUser.inc:1122 ../lib/modules/windowsUser.inc:2686 +#: ../lib/modules/windowsUser.inc:3607 msgid "User name (pre W2K)" msgstr "Benutzername (vor W2K)" @@ -18561,13 +18587,13 @@ msgstr "Benutzername (vor W2K)" msgid "User name already exists!" msgstr "Benutzername besteht schon!" -#: ../lib/modules/passwordSelfReset.inc:631 +#: ../lib/modules/passwordSelfReset.inc:629 msgid "User name and email address" msgstr "Benutzername und Email-Adresse" -#: ../templates/config/confmain.php:484 -#: ../templates/selfService/adminMain.php:504 ../lib/modules/imapAccess.inc:107 -#: ../lib/modules/imapAccess.inc:536 ../help/help.inc:326 +#: ../templates/config/confmain.php:494 +#: ../templates/selfService/adminMain.php:513 ../lib/modules/imapAccess.inc:107 +#: ../lib/modules/imapAccess.inc:536 ../help/help.inc:330 msgid "User name attribute" msgstr "Attribut für Benutzernamen" @@ -18585,10 +18611,10 @@ msgstr "Attribut für Benutzernamen" #: ../lib/modules/windowsUser.inc:990 ../lib/modules/windowsUser.inc:991 #: ../lib/modules/pykotaUser.inc:274 ../lib/modules/pykotaUser.inc:275 #: ../lib/modules/pykotaUser.inc:278 ../lib/modules/pykotaUser.inc:279 -#: ../lib/modules/nisnetgroup.inc:155 ../lib/modules/inetOrgPerson.inc:100 +#: ../lib/modules/nisnetgroup.inc:154 ../lib/modules/inetOrgPerson.inc:100 #: ../lib/modules/inetOrgPerson.inc:101 ../lib/modules/kopanoUser.inc:442 -#: ../lib/modules/kopanoUser.inc:443 ../lib/modules/passwordSelfReset.inc:1104 -#: ../lib/modules/passwordSelfReset.inc:1355 +#: ../lib/modules/kopanoUser.inc:443 ../lib/modules/passwordSelfReset.inc:1102 +#: ../lib/modules/passwordSelfReset.inc:1353 #: ../lib/modules/heimdalKerberos.inc:245 #: ../lib/modules/heimdalKerberos.inc:246 msgid "" @@ -18627,7 +18653,7 @@ msgstr "" "Benutzername des zu erstellenden Benutzer. Gültige Zeichen sind: a-z, A-Z, " "0-9 und @.-_." -#: ../lib/modules/posixAccount.inc:411 +#: ../lib/modules/posixAccount.inc:416 msgid "" "User name of the user who should be created. Valid characters are: a-z,A-" "Z,0-9, @.-_. If user name is already used user name will be expanded with a " @@ -18637,13 +18663,13 @@ msgstr "" "0-9 und @.-_. Sollte der Benutzername schon in Verwendung sein wird eine " "Zahl angehängt." -#: ../lib/modules/passwordSelfReset.inc:632 -#: ../lib/modules/passwordSelfReset.inc:1064 -#: ../lib/modules/passwordSelfReset.inc:1271 +#: ../lib/modules/passwordSelfReset.inc:630 +#: ../lib/modules/passwordSelfReset.inc:1062 +#: ../lib/modules/passwordSelfReset.inc:1269 msgid "User name or email address" msgstr "Benutzername oder Email-Adresse" -#: ../lib/modules/posixAccount.inc:314 ../lib/modules/posixAccount.inc:2247 +#: ../lib/modules/posixAccount.inc:319 ../lib/modules/posixAccount.inc:2205 msgid "User name suggestion" msgstr "Vorschlag für Benutzername" @@ -18651,7 +18677,7 @@ msgstr "Vorschlag für Benutzername" msgid "User name that is used for PyKota." msgstr "Benutzername für PyKota." -#: ../lib/modules/windowsUser.inc:1247 +#: ../lib/modules/windowsUser.inc:1254 msgid "User profile" msgstr "Benutzerprofil" @@ -18664,18 +18690,18 @@ msgstr "Benutzerselbstregistrierung" #: ../lib/modules/zarafaUser.inc:1274 ../lib/modules/zarafaUser.inc:1364 #: ../lib/modules/zarafaUser.inc:1831 ../lib/modules/kopanoUser.inc:146 #: ../lib/modules/kopanoUser.inc:307 ../lib/modules/kopanoUser.inc:399 -#: ../lib/modules/kopanoUser.inc:646 ../lib/modules/kopanoUser.inc:1274 -#: ../lib/modules/kopanoUser.inc:1364 ../lib/modules/kopanoUser.inc:1842 +#: ../lib/modules/kopanoUser.inc:608 ../lib/modules/kopanoUser.inc:1193 +#: ../lib/modules/kopanoUser.inc:1283 ../lib/modules/kopanoUser.inc:1761 msgid "User server" msgstr "Benutzerserver" #: ../lib/types/user.inc:55 ../lib/modules/posixAccount.inc:83 #: ../lib/modules/posixAccount.inc:84 ../lib/modules/posixAccount.inc:87 -#: ../lib/modules/posixAccount.inc:2201 ../lib/modules/zarafaUser.inc:1803 +#: ../lib/modules/posixAccount.inc:2159 ../lib/modules/zarafaUser.inc:1803 #: ../lib/modules/sudoRole.inc:73 ../lib/modules/sudoRole.inc:97 #: ../lib/modules/sudoRole.inc:158 ../lib/modules/sudoRole.inc:215 -#: ../lib/modules/sudoRole.inc:227 ../lib/modules/sudoRole.inc:315 -#: ../lib/modules/sudoRole.inc:747 ../lib/modules/kopanoUser.inc:1814 +#: ../lib/modules/sudoRole.inc:227 ../lib/modules/sudoRole.inc:300 +#: ../lib/modules/sudoRole.inc:454 ../lib/modules/kopanoUser.inc:1733 msgid "Users" msgstr "Benutzer" @@ -18685,7 +18711,7 @@ msgstr "Benutzer" msgid "Users or groups that may directly send email as this user." msgstr "Benutzer oder Gruppen die EMails als dieser Benutzer senden dürfen." -#: ../lib/modules/posixGroup.inc:478 +#: ../lib/modules/posixGroup.inc:427 msgid "" "Users who are member of the current group. Users who have set their primary " "group to this group will not be shown." @@ -18693,7 +18719,7 @@ msgstr "" "Hier können Sie Benutzer zu dieser Gruppe hinzufügen und entfernen. " "Benutzer, deren primäre Gruppe diese Gruppe ist, werden nicht angezeigt." -#: ../lib/modules/posixGroup.inc:482 +#: ../lib/modules/posixGroup.inc:431 msgid "" "Users who will become member of the current group. User names are separated " "by semicolons." @@ -18701,17 +18727,17 @@ msgstr "" "Benutzer die Mitglieder der aktuellen Gruppe werden. Benutzernamen werden " "durch Komma getrennt." -#: ../templates/tests/lamdaemonTest.php:208 +#: ../templates/tests/lamdaemonTest.php:209 #, php-format msgid "Using %s as lamdaemon remote server." msgstr "Es wird %s als Lamdaemon-Server verwendet." -#: ../templates/tests/lamdaemonTest.php:229 +#: ../templates/tests/lamdaemonTest.php:230 #, php-format msgid "Using %s to connect to remote server." msgstr "Für die Verbindung zum Remote-Server wird %s verwendet." -#: ../lib/modules/posixAccount.inc:324 +#: ../lib/modules/posixAccount.inc:329 msgid "" "Usually, users are not added to groups as memberUid if they have this group " "as primary group. If your application ignores primary groups then you can " @@ -18746,7 +18772,7 @@ msgstr "Validierungsausdruck" msgid "Validation message" msgstr "Validierungsnachricht" -#: ../templates/tools/multiEdit.php:142 ../lib/modules/customFields.inc:209 +#: ../templates/tools/multiEdit.php:144 ../lib/modules/customFields.inc:209 #: ../lib/modules/customFields.inc:3640 ../lib/modules/customFields.inc:4732 #: ../lib/modules/customFields.inc:4751 msgid "Value" @@ -18774,8 +18800,8 @@ msgstr "Die Werte dieses Attributes werden in der Auswahlliste angezeigt." #: ../lib/modules/puppetClient.inc:107 ../lib/modules/puppetClient.inc:111 #: ../lib/modules/puppetClient.inc:158 ../lib/modules/puppetClient.inc:168 -#: ../lib/modules/puppetClient.inc:181 ../lib/modules/puppetClient.inc:288 -#: ../lib/modules/puppetClient.inc:415 +#: ../lib/modules/puppetClient.inc:181 ../lib/modules/puppetClient.inc:285 +#: ../lib/modules/puppetClient.inc:411 msgid "Variables" msgstr "Variablen" @@ -18787,7 +18813,7 @@ msgstr "Herstellername" msgid "Vendor version" msgstr "Herstellerversion" -#: ../lib/modules/nsview.inc:46 +#: ../lib/modules/nsview.inc:45 msgid "View" msgstr "Ansicht" @@ -18842,27 +18868,27 @@ msgstr "Sie möchten mehr Funktionen? Bestellen Sie LAM Pro!" msgid "Warning" msgstr "Warnung" -#: ../templates/delete.php:275 ../lib/modules/nisMailAliasUser.inc:508 -#: ../lib/modules/posixAccount.inc:854 +#: ../templates/delete.php:275 ../lib/modules/nisMailAliasUser.inc:501 +#: ../lib/modules/posixAccount.inc:841 #: ../lib/modules/organizationalRoleUser.inc:197 #: ../lib/modules/organizationalRoleUser.inc:214 -#: ../lib/modules/windowsUser.inc:2180 ../lib/modules/groupOfNamesUser.inc:224 -#: ../lib/modules/groupOfNamesUser.inc:269 ../lib/modules/windowsGroup.inc:1044 -#: ../lib/modules.inc:2026 +#: ../lib/modules/windowsUser.inc:2152 ../lib/modules/groupOfNamesUser.inc:224 +#: ../lib/modules/groupOfNamesUser.inc:269 ../lib/modules/windowsGroup.inc:1025 +#: ../lib/modules.inc:2033 #, php-format msgid "Was unable to add attributes to DN: %s." msgstr "Konnte keine Attribute zum DN hinzufügen: %s." -#: ../lib/modules/nisMailAliasUser.inc:497 ../lib/modules/range.inc:659 -#: ../lib/modules/inetOrgPerson.inc:938 ../lib/modules/inetOrgPerson.inc:2442 +#: ../lib/modules/nisMailAliasUser.inc:490 ../lib/modules/range.inc:659 +#: ../lib/modules/inetOrgPerson.inc:938 ../lib/modules/inetOrgPerson.inc:2443 #: ../lib/import.inc:461 ../lib/import.inc:618 ../lib/import.inc:680 -#: ../lib/import.inc:743 ../lib/modules.inc:1989 +#: ../lib/import.inc:743 ../lib/modules.inc:1995 #, php-format msgid "Was unable to create DN: %s." msgstr "Konnte DN nicht erstellen: %s." -#: ../lib/account.inc:959 ../lib/account.inc:967 -#: ../lib/modules/nisMailAliasUser.inc:474 ../lib/modules/range.inc:726 +#: ../lib/account.inc:971 ../lib/account.inc:979 +#: ../lib/modules/nisMailAliasUser.inc:467 ../lib/modules/range.inc:726 #, php-format msgid "Was unable to delete DN: %s." msgstr "Konnte DN nicht löschen: %s." @@ -18874,49 +18900,49 @@ msgstr "Konnte DN nicht löschen: %s." msgid "Was unable to modify attributes from DN: %s." msgstr "Konnte die Attribute von DN %s nicht ändern." -#: ../lib/modules/posixAccount.inc:838 ../lib/modules/windowsUser.inc:2206 -#: ../lib/modules/windowsUser.inc:2684 ../lib/modules/range.inc:703 +#: ../lib/modules/posixAccount.inc:825 ../lib/modules/windowsUser.inc:2178 +#: ../lib/modules/windowsUser.inc:2656 ../lib/modules/range.inc:703 #: ../lib/modules/nisNetGroupUser.inc:355 #: ../lib/modules/nisNetGroupUser.inc:372 #: ../lib/modules/nisNetGroupUser.inc:407 #: ../lib/modules/nisNetGroupUser.inc:419 #: ../lib/modules/nisNetGroupUser.inc:566 #: ../lib/modules/groupOfNamesUser.inc:254 ../lib/modules/customFields.inc:4826 -#: ../lib/modules.inc:2008 +#: ../lib/modules.inc:2015 #, php-format msgid "Was unable to modify attributes of DN: %s." msgstr "Konnte die Attribute von DN %s nicht ändern." -#: ../templates/delete.php:284 ../lib/modules/nisMailAliasUser.inc:485 -#: ../lib/modules/posixAccount.inc:871 +#: ../templates/delete.php:284 ../lib/modules/nisMailAliasUser.inc:478 +#: ../lib/modules/posixAccount.inc:858 #: ../lib/modules/organizationalRoleUser.inc:202 #: ../lib/modules/organizationalRoleUser.inc:225 -#: ../lib/modules/windowsUser.inc:2193 ../lib/modules/groupOfNamesUser.inc:219 -#: ../lib/modules/groupOfNamesUser.inc:283 ../lib/modules/windowsGroup.inc:1057 -#: ../lib/modules.inc:2039 +#: ../lib/modules/windowsUser.inc:2165 ../lib/modules/groupOfNamesUser.inc:219 +#: ../lib/modules/groupOfNamesUser.inc:283 ../lib/modules/windowsGroup.inc:1038 +#: ../lib/modules.inc:2046 #, php-format msgid "Was unable to remove attributes from DN: %s." msgstr "Konnte die Attribute nicht löschen: %s." -#: ../lib/modules/asteriskExtension.inc:875 ../lib/modules.inc:1969 +#: ../lib/modules/asteriskExtension.inc:873 ../lib/modules.inc:1975 #, php-format msgid "Was unable to rename DN: %s." msgstr "Konnte DN nicht umbenennen: %s." #: ../lib/modules/windowsUser.inc:213 ../lib/modules/windowsUser.inc:520 #: ../lib/modules/windowsUser.inc:857 ../lib/modules/windowsUser.inc:945 -#: ../lib/modules/windowsUser.inc:1163 ../lib/modules/windowsUser.inc:2738 -#: ../lib/modules/windowsUser.inc:2929 ../lib/modules/inetOrgPerson.inc:164 +#: ../lib/modules/windowsUser.inc:1162 ../lib/modules/windowsUser.inc:2710 +#: ../lib/modules/windowsUser.inc:2901 ../lib/modules/inetOrgPerson.inc:164 #: ../lib/modules/inetOrgPerson.inc:405 ../lib/modules/inetOrgPerson.inc:528 #: ../lib/modules/inetOrgPerson.inc:673 ../lib/modules/inetOrgPerson.inc:677 #: ../lib/modules/inetOrgPerson.inc:1435 ../lib/modules/inetOrgPerson.inc:1438 -#: ../lib/modules/inetOrgPerson.inc:1976 ../lib/modules/inetOrgPerson.inc:2042 -#: ../lib/modules/inetOrgPerson.inc:2581 ../lib/modules/inetOrgPerson.inc:3884 -#: ../lib/modules/inetOrgPerson.inc:3911 +#: ../lib/modules/inetOrgPerson.inc:1977 ../lib/modules/inetOrgPerson.inc:2043 +#: ../lib/modules/inetOrgPerson.inc:2582 ../lib/modules/inetOrgPerson.inc:3885 +#: ../lib/modules/inetOrgPerson.inc:3912 msgid "Web site" msgstr "Homepage" -#: ../lib/modules/sambaSamAccount.inc:1336 +#: ../lib/modules/sambaSamAccount.inc:1386 msgid "Wednesday" msgstr "Mittwoch" @@ -18963,24 +18989,24 @@ msgstr "" "selbe IP/Domäne wie in Ihrem Zertifikat verwenden!" #: ../templates/lists/changePassword.php:421 -#: ../templates/lists/changePassword.php:455 ../lib/types/user.inc:326 -#: ../lib/types/user.inc:375 ../lib/types/user.inc:472 -#: ../lib/types/user.inc:515 ../lib/types/user.inc:1110 -#: ../lib/types/user.inc:1112 ../lib/modules/windowsHost.inc:64 -#: ../lib/modules/windowsUser.inc:108 ../lib/modules/windowsUser.inc:3874 -#: ../lib/modules/windowsUser.inc:4021 ../lib/modules/windowsUser.inc:4112 +#: ../templates/lists/changePassword.php:455 ../lib/types/user.inc:327 +#: ../lib/types/user.inc:376 ../lib/types/user.inc:473 +#: ../lib/types/user.inc:516 ../lib/types/user.inc:1111 +#: ../lib/types/user.inc:1113 ../lib/modules/windowsHost.inc:63 +#: ../lib/modules/windowsUser.inc:108 ../lib/modules/windowsUser.inc:3846 +#: ../lib/modules/windowsUser.inc:3993 ../lib/modules/windowsUser.inc:4084 #: ../lib/modules/windowsGroup.inc:104 msgid "Windows" msgstr "Windows" -#: ../lib/modules/posixGroup.inc:601 ../lib/modules/posixAccount.inc:2185 +#: ../lib/modules/posixGroup.inc:550 ../lib/modules/posixAccount.inc:2143 msgid "Windows domain info" msgstr "Windows-Domäneninfo" -#: ../lib/modules/posixGroup.inc:518 ../lib/modules/posixGroup.inc:628 -#: ../lib/modules/posixGroup.inc:767 ../lib/modules/posixAccount.inc:122 -#: ../lib/modules/posixAccount.inc:389 ../lib/modules/posixAccount.inc:2234 -#: ../lib/modules/posixAccount.inc:2305 +#: ../lib/modules/posixGroup.inc:467 ../lib/modules/posixGroup.inc:577 +#: ../lib/modules/posixGroup.inc:716 ../lib/modules/posixAccount.inc:122 +#: ../lib/modules/posixAccount.inc:394 ../lib/modules/posixAccount.inc:2192 +#: ../lib/modules/posixAccount.inc:2263 msgid "Windows domain info DN" msgstr "Windows-Domäneninfo DN" @@ -18990,12 +19016,12 @@ msgstr "Windows-Domänenname des Accounts." #: ../lib/modules/sambaSamAccount.inc:213 #: ../lib/modules/sambaSamAccount.inc:478 -#: ../lib/modules/sambaSamAccount.inc:1206 -#: ../lib/modules/sambaSamAccount.inc:1751 -#: ../lib/modules/sambaSamAccount.inc:1929 +#: ../lib/modules/sambaSamAccount.inc:1251 +#: ../lib/modules/sambaSamAccount.inc:1798 +#: ../lib/modules/sambaSamAccount.inc:1976 #: ../lib/modules/sambaGroupMapping.inc:111 #: ../lib/modules/sambaGroupMapping.inc:349 -#: ../lib/modules/sambaGroupMapping.inc:515 +#: ../lib/modules/sambaGroupMapping.inc:512 msgid "Windows group" msgstr "Windows-Gruppe" @@ -19029,18 +19055,18 @@ msgstr "Primäre Windows-Gruppen-SID" msgid "Windows-Domain name of group." msgstr "Windows-Domänenname der Gruppe." -#: ../lib/modules/windowsUser.inc:1167 ../lib/modules/inetOrgPerson.inc:1446 +#: ../lib/modules/windowsUser.inc:1166 ../lib/modules/inetOrgPerson.inc:1446 msgid "Work details" msgstr "Arbeitsdaten" -#: ../lib/modules/sambaDomain.inc:146 +#: ../lib/modules/sambaDomain.inc:145 msgid "Workgroup" msgstr "Arbeitsgruppe" #: ../lib/modules/sambaSamAccount.inc:237 #: ../lib/modules/sambaSamAccount.inc:368 -#: ../lib/modules/sambaSamAccount.inc:1475 -#: ../lib/modules/sambaSamAccount.inc:1949 +#: ../lib/modules/sambaSamAccount.inc:1536 +#: ../lib/modules/sambaSamAccount.inc:1996 msgid "Working directory" msgstr "Arbeitsverzeichnis" @@ -19048,17 +19074,17 @@ msgstr "Arbeitsverzeichnis" msgid "Working directory of initial program." msgstr "Arbeitsverzeichnis des Startprogramms." -#: ../templates/config/confmain.php:298 +#: ../templates/config/confmain.php:302 msgid "Write" msgstr "Schreiben" -#: ../templates/config/confmain.php:211 +#: ../templates/config/confmain.php:215 msgid "Write access" msgstr "Schreibzugriff" #: ../templates/login.php:523 ../templates/login.php:588 #: ../templates/selfService/selfServiceLogin.php:155 -#: ../templates/selfService/selfServiceLogin.php:190 +#: ../templates/selfService/selfServiceLogin.php:191 msgid "Wrong password/user name combination. Please try again." msgstr "Falsche Benutzername/Passwort-Kombination. Bitte erneut eingeben." @@ -19071,27 +19097,27 @@ msgstr "Das Quota-Format ist falsch. Es muss numerisch sein." #: ../lib/modules/courierMailAccount.inc:445 ../lib/modules/freeRadius.inc:160 #: ../lib/modules/freeRadius.inc:402 ../lib/modules/freeRadius.inc:699 #: ../lib/modules/qmailGroup.inc:287 ../lib/modules/qmailGroup.inc:329 -#: ../lib/modules/qmailGroup.inc:946 ../lib/modules/qmailGroup.inc:947 -#: ../lib/modules/qmailGroup.inc:990 ../lib/modules/qmailGroup.inc:996 +#: ../lib/modules/qmailGroup.inc:797 ../lib/modules/qmailGroup.inc:798 +#: ../lib/modules/qmailGroup.inc:841 ../lib/modules/qmailGroup.inc:847 #: ../lib/modules/imapAccess.inc:512 msgid "Yes" msgstr "Ja" -#: ../lib/modules/sambaSamAccount.inc:2474 +#: ../lib/modules/sambaSamAccount.inc:2521 msgid "You are not yet allowed to change your password." msgstr "Sie dürfen das Passwort noch nicht ändern." #: ../templates/lists/changePassword.php:543 #: ../templates/lists/changePassword.php:553 -#: ../lib/modules/sambaSamAccount.inc:2437 -#: ../lib/modules/sambaSamAccount.inc:2531 ../lib/modules/ppolicyUser.inc:338 +#: ../lib/modules/sambaSamAccount.inc:2484 +#: ../lib/modules/sambaSamAccount.inc:2578 ../lib/modules/ppolicyUser.inc:338 #: ../lib/modules/ppolicyUser.inc:418 msgid "You are reusing an old password. Please choose a different password." msgstr "" "Sie versuchen ein altes Passwort wiederzuverwenden. Bitte wählen Sie ein " "anderes Passwort." -#: ../lib/modules/account.inc:131 ../lib/modules/posixGroup.inc:760 +#: ../lib/modules/account.inc:131 ../lib/modules/posixGroup.inc:709 #: ../lib/modules/posixAccount.inc:103 ../lib/modules/posixAccount.inc:105 msgid "" "You are using capital letters. This can cause problems because Windows is " @@ -19100,11 +19126,11 @@ msgstr "" "Sie verwenden Großbuchstaben, das kann Probleme verursachen, da Windows " "Groß-/Kleinschreibung nicht unterscheidet." -#: ../help/help.inc:407 +#: ../help/help.inc:413 msgid "You can delete or move expired accounts." msgstr "Sie können abgelaufene Konten löschen oder verschieben." -#: ../lib/lists.inc:1254 +#: ../lib/lists.inc:1253 #, php-format msgid "" "You can download your PDF files {link=%s}{color=#d2131a}here{endcolor}" @@ -19113,7 +19139,7 @@ msgstr "" "Sie können Ihre PDF-Dateien {link=%s}{color=#d2131a}hier{endcolor}{endlink} " "herunterladen." -#: ../help/help.inc:299 +#: ../help/help.inc:301 msgid "You can enable 2-factor authentication here (e.g. via mobile device)." msgstr "Damit aktivieren Sie die 2-Faktor-Authentifizierung (z.B. per Handy)." @@ -19159,8 +19185,8 @@ msgid "You can use this to temporarily deactivate the Zarafa extension." msgstr "Sie können hiermit die Zarafa-Erweiterung temporär deaktivieren." #: ../lib/modules/passwordSelfReset.inc:129 -#: ../lib/modules/passwordSelfReset.inc:139 ../help/help.inc:335 -#: ../help/help.inc:386 +#: ../lib/modules/passwordSelfReset.inc:139 ../help/help.inc:341 +#: ../help/help.inc:392 msgid "" "You can use wildcards for LDAP attributes in the form @@attribute@@ (e.g. " "@@uid@@ for the user name)." @@ -19168,7 +19194,7 @@ msgstr "" "Sie können Platzhalter für LDAP-Attribute im Format @@Attribut@@ (z.B. " "@@uid@@ für den Benutzernamen) verwenden." -#: ../lib/modules/customScripts.inc:65 ../lib/modules/customScripts.inc:78 +#: ../lib/modules/customScripts.inc:64 ../lib/modules/customScripts.inc:77 msgid "" "You can use wildcards in the format $wildcard$ which are replaced by the " "LDAP attribute with the same name. For multi-value attributes the values " @@ -19200,7 +19226,7 @@ msgstr "Sie können im Nur-Lesen-Modus keine Änderungen vornehmen." msgid "You cannot rename an entry which has child entries." msgstr "Sie können keinen Eintrag mit Unterelementen umbenennen." -#: ../templates/config/confmain.php:554 +#: ../templates/config/confmain.php:569 msgid "" "You cannot use SSL and TLS encryption at the same time. Please use either " "\"ldaps://\" or TLS." @@ -19284,7 +19310,7 @@ msgstr "Sie werden um eine Bestätigung für diese Entscheidung gefragt werden" msgid "Your IMAP domains and email address domain do not match." msgstr "Ihre IMAP-Domänen stimmen nicht mit der EMail-Adresse überein." -#: ../templates/tests/lamdaemonTest.php:233 ../lib/remote.inc:115 +#: ../templates/tests/lamdaemonTest.php:234 ../lib/remote.inc:119 #, php-format msgid "" "Your LAM admin user (%s) must be a valid Unix account to work with lamdaemon!" @@ -19310,8 +19336,8 @@ msgid "" "PHP." msgstr "Sie müssen Dateiuploads in der php.ini (file_uploads=ON) aktivieren." -#: ../templates/config/confmain.php:161 -#: ../templates/selfService/adminMain.php:369 +#: ../templates/config/confmain.php:165 +#: ../templates/selfService/adminMain.php:375 msgid "" "Your changes cannot be saved until you make the file writable for the " "webserver user." @@ -19319,7 +19345,7 @@ msgstr "" "Ihre Änderungen können erst gespeichert werden wenn Sie die Datei für den " "Webserver schreibbar machen." -#: ../templates/login.php:456 ../templates/selfService/selfServiceLogin.php:379 +#: ../templates/login.php:456 ../templates/selfService/selfServiceLogin.php:380 #, php-format msgid "" "Your licence expires on %s. You need to purchase a new licence to be able to " @@ -19344,8 +19370,8 @@ msgstr "" "Ihr neuer Benutzer wurde erfolgreich angelegt. Bitte gehen Sie zum Login " "zurück um Ihre persönlichen Daten zu ändern." -#: ../lib/account.inc:1460 ../lib/modules/windowsUser.inc:3078 -#: ../lib/modules/windowsUser.inc:3152 +#: ../lib/account.inc:1475 ../lib/modules/windowsUser.inc:3050 +#: ../lib/modules/windowsUser.inc:3124 msgid "" "Your password does not meet the password strength qualifications. Please " "retry with another one." @@ -19353,7 +19379,7 @@ msgstr "" "Ihr Passwort erfüllt nicht die Passwortrichtlinien. Bitte versuchen Sie ein " "anderes." -#: ../lib/modules/passwordSelfReset.inc:684 +#: ../lib/modules/passwordSelfReset.inc:682 msgid "Your password was changed to @@newPassword@@." msgstr "Ihr Passwort wurde auf @@newPassword@@ geändert." @@ -19381,14 +19407,15 @@ msgstr "Ihre Einstellungen wurden erfolgreich gespeichert." msgid "YourCompany" msgstr "IhreFirma" -#: ../lib/2factor.inc:357 +#: ../lib/2factor.inc:364 msgid "YubiKey id does not match allowed list of key ids." msgstr "Die YubKey ID ist nicht in der Liste der erlaubten IDs." #: ../lib/modules/yubiKeyUser.inc:88 ../lib/modules/yubiKeyUser.inc:92 -#: ../lib/modules/yubiKeyUser.inc:108 ../lib/modules/yubiKeyUser.inc:115 -#: ../lib/modules/yubiKeyUser.inc:119 ../lib/modules/yubiKeyUser.inc:149 -#: ../lib/modules/yubiKeyUser.inc:245 ../lib/modules/yubiKeyUser.inc:289 +#: ../lib/modules/yubiKeyUser.inc:104 ../lib/modules/yubiKeyUser.inc:112 +#: ../lib/modules/yubiKeyUser.inc:119 ../lib/modules/yubiKeyUser.inc:123 +#: ../lib/modules/yubiKeyUser.inc:153 ../lib/modules/yubiKeyUser.inc:247 +#: ../lib/modules/yubiKeyUser.inc:290 msgid "YubiKey ids" msgstr "YubiKey IDs" @@ -19461,8 +19488,8 @@ msgstr "Wert hinzufügen" #: ../lib/modules/dynamicList.inc:84 ../lib/modules/zarafaDynamicGroup.inc:117 #: ../lib/modules/posixGroup.inc:68 ../lib/modules/zarafaAddressList.inc:105 -#: ../lib/modules/pykotaGroup.inc:144 ../lib/modules/pykotaGroup.inc:388 -#: ../lib/modules/kopanoDynamicGroup.inc:121 ../lib/modules/nisnetgroup.inc:117 +#: ../lib/modules/pykotaGroup.inc:144 ../lib/modules/pykotaGroup.inc:387 +#: ../lib/modules/kopanoDynamicGroup.inc:121 ../lib/modules/nisnetgroup.inc:116 #: ../lib/modules/kopanoAddressList.inc:105 #: ../lib/modules/organizationalRole.inc:120 #: ../lib/modules/windowsGroup.inc:232 ../lib/modules/groupOfNames.inc:131 @@ -19484,7 +19511,7 @@ msgstr "Attribut gelöscht" msgid "backupRootFileSystem" msgstr "sichereRootDateisystem" -#: ../lib/modules/pykotaBillingCode.inc:103 +#: ../lib/modules/pykotaBillingCode.inc:102 msgid "billingCode01" msgstr "rechnungscode01" @@ -19522,17 +19549,17 @@ msgstr "Löschen" msgid "delete attribute" msgstr "Attribut löschen" -#: ../templates/selfService/adminMain.php:530 -#: ../lib/modules/sambaSamAccount.inc:1496 -#: ../lib/modules/sambaSamAccount.inc:1969 ../lib/modules/zarafaUser.inc:651 +#: ../templates/selfService/adminMain.php:542 +#: ../lib/modules/sambaSamAccount.inc:1557 +#: ../lib/modules/sambaSamAccount.inc:2016 ../lib/modules/zarafaUser.inc:651 #: ../lib/modules/zarafaUser.inc:1326 ../lib/modules/zarafaUser.inc:1367 -#: ../lib/modules/kopanoUser.inc:651 ../lib/modules/kopanoUser.inc:1326 -#: ../lib/modules/kopanoUser.inc:1367 +#: ../lib/modules/kopanoUser.inc:612 ../lib/modules/kopanoUser.inc:1245 +#: ../lib/modules/kopanoUser.inc:1286 msgid "disabled" msgstr "deaktiviert" -#: ../lib/modules/sambaSamAccount.inc:1507 -#: ../lib/modules/sambaSamAccount.inc:1980 +#: ../lib/modules/sambaSamAccount.inc:1568 +#: ../lib/modules/sambaSamAccount.inc:2027 msgid "disconnect" msgstr "trennen" @@ -19554,8 +19581,8 @@ msgid "edit" msgstr "Ändern" #: ../lib/modules/zarafaUser.inc:651 ../lib/modules/zarafaUser.inc:1321 -#: ../lib/modules/zarafaUser.inc:1367 ../lib/modules/kopanoUser.inc:651 -#: ../lib/modules/kopanoUser.inc:1321 ../lib/modules/kopanoUser.inc:1367 +#: ../lib/modules/zarafaUser.inc:1367 ../lib/modules/kopanoUser.inc:612 +#: ../lib/modules/kopanoUser.inc:1240 ../lib/modules/kopanoUser.inc:1286 msgid "enabled" msgstr "aktiviert" @@ -19573,22 +19600,22 @@ msgstr "fehlgeschlagen" msgid "false" msgstr "falsch" -#: ../lib/modules/ppolicy.inc:37 +#: ../lib/modules/ppolicy.inc:36 msgid "force" msgstr "erzwingen" -#: ../lib/modules/sambaSamAccount.inc:1515 -#: ../lib/modules/sambaSamAccount.inc:1988 +#: ../lib/modules/sambaSamAccount.inc:1576 +#: ../lib/modules/sambaSamAccount.inc:2035 msgid "from any client" msgstr "von jedem Client" -#: ../lib/modules/sambaSamAccount.inc:1516 -#: ../lib/modules/sambaSamAccount.inc:1989 +#: ../lib/modules/sambaSamAccount.inc:1577 +#: ../lib/modules/sambaSamAccount.inc:2036 msgid "from previous client only" msgstr "nur vom letzten Client" -#: ../lib/modules/posixAccount.inc:228 ../lib/modules/posixAccount.inc:258 -#: ../lib/modules/nisnetgroup.inc:131 ../lib/modules/groupOfNamesUser.inc:77 +#: ../lib/modules/posixAccount.inc:233 ../lib/modules/posixAccount.inc:263 +#: ../lib/modules/nisnetgroup.inc:130 ../lib/modules/groupOfNamesUser.inc:77 msgid "group01,group02" msgstr "gruppe01,gruppe02" @@ -19613,7 +19640,7 @@ msgstr "gruppe@firma.de" msgid "hint" msgstr "Hinweis" -#: ../lib/modules/quota.inc:211 ../lib/modules/quota.inc:217 +#: ../lib/modules/quota.inc:214 ../lib/modules/quota.inc:220 msgid "hours" msgstr "Stunden" @@ -19631,23 +19658,23 @@ msgstr "Import" msgid "in progress" msgstr "in Arbeit" -#: ../lib/modules/sambaSamAccount.inc:1500 -#: ../lib/modules/sambaSamAccount.inc:1973 +#: ../lib/modules/sambaSamAccount.inc:1561 +#: ../lib/modules/sambaSamAccount.inc:2020 msgid "input off, notify off" msgstr "Eingabe aus, Benachrichtigung aus" -#: ../lib/modules/sambaSamAccount.inc:1499 -#: ../lib/modules/sambaSamAccount.inc:1972 +#: ../lib/modules/sambaSamAccount.inc:1560 +#: ../lib/modules/sambaSamAccount.inc:2019 msgid "input off, notify on" msgstr "Eingabe aus, Benachrichtigung ein" -#: ../lib/modules/sambaSamAccount.inc:1498 -#: ../lib/modules/sambaSamAccount.inc:1971 +#: ../lib/modules/sambaSamAccount.inc:1559 +#: ../lib/modules/sambaSamAccount.inc:2018 msgid "input on, notify off" msgstr "Eingabe ein, Benachrichtigung aus" -#: ../lib/modules/sambaSamAccount.inc:1497 -#: ../lib/modules/sambaSamAccount.inc:1970 +#: ../lib/modules/sambaSamAccount.inc:1558 +#: ../lib/modules/sambaSamAccount.inc:2017 msgid "input on, notify on" msgstr "Eingabe ein, Benachrichtigung ein" @@ -19675,7 +19702,7 @@ msgstr "Liste" msgid "login" msgstr "Login" -#: ../lib/modules/posixAccount.inc:283 +#: ../lib/modules/posixAccount.inc:288 msgid "machines" msgstr "Hosts" @@ -19696,9 +19723,9 @@ msgstr "meineGruppe" msgid "new" msgstr "neu" -#: ../templates/config/confmain.php:193 ../templates/schema/schema.php:339 +#: ../templates/config/confmain.php:197 ../templates/schema/schema.php:339 #: ../templates/schema/schema.php:341 ../templates/schema/schema.php:343 -#: ../lib/modules/kopanoServer.inc:331 +#: ../lib/modules/kopanoServer.inc:311 #: ../lib/modules/zarafaDynamicGroup.inc:417 #: ../lib/modules/zarafaDynamicGroup.inc:420 #: ../lib/modules/zarafaContact.inc:638 ../lib/modules/zarafaGroup.inc:653 @@ -19706,32 +19733,32 @@ msgstr "neu" #: ../lib/modules/kopanoGroup.inc:604 ../lib/modules/kopanoGroup.inc:611 #: ../lib/modules/kopanoGroup.inc:614 ../lib/modules/zarafaAddressList.inc:317 #: ../lib/modules/zarafaAddressList.inc:320 -#: ../lib/modules/sambaSamAccount.inc:1886 -#: ../lib/modules/sambaSamAccount.inc:1892 -#: ../lib/modules/sambaSamAccount.inc:1937 -#: ../lib/modules/sambaSamAccount.inc:1945 -#: ../lib/modules/sambaSamAccount.inc:1955 -#: ../lib/modules/sambaSamAccount.inc:1960 -#: ../lib/modules/sambaSamAccount.inc:1965 -#: ../lib/modules/sambaSamAccount.inc:2275 ../lib/modules/zarafaUser.inc:633 +#: ../lib/modules/sambaSamAccount.inc:1933 +#: ../lib/modules/sambaSamAccount.inc:1939 +#: ../lib/modules/sambaSamAccount.inc:1984 +#: ../lib/modules/sambaSamAccount.inc:1992 +#: ../lib/modules/sambaSamAccount.inc:2002 +#: ../lib/modules/sambaSamAccount.inc:2007 +#: ../lib/modules/sambaSamAccount.inc:2012 +#: ../lib/modules/sambaSamAccount.inc:2322 ../lib/modules/zarafaUser.inc:633 #: ../lib/modules/zarafaUser.inc:1279 ../lib/modules/zarafaUser.inc:1294 #: ../lib/modules/zarafaUser.inc:1305 ../lib/modules/zarafaUser.inc:1311 -#: ../lib/modules/kopanoContact.inc:593 ../lib/modules/windowsUser.inc:534 +#: ../lib/modules/kopanoContact.inc:590 ../lib/modules/windowsUser.inc:534 #: ../lib/modules/windowsUser.inc:535 ../lib/modules/windowsUser.inc:536 #: ../lib/modules/windowsUser.inc:542 ../lib/modules/windowsUser.inc:543 #: ../lib/modules/windowsUser.inc:544 ../lib/modules/windowsUser.inc:556 #: ../lib/modules/windowsUser.inc:557 ../lib/modules/windowsUser.inc:558 #: ../lib/modules/windowsUser.inc:564 ../lib/modules/windowsUser.inc:565 -#: ../lib/modules/windowsUser.inc:566 ../lib/modules/windowsUser.inc:2236 -#: ../lib/modules/windowsUser.inc:2751 ../lib/modules/windowsUser.inc:2756 -#: ../lib/modules/windowsUser.inc:2762 ../lib/modules/windowsUser.inc:2767 -#: ../lib/modules/ppolicy.inc:35 ../lib/modules/ppolicy.inc:480 -#: ../lib/modules/ppolicy.inc:490 ../lib/modules/ppolicy.inc:495 -#: ../lib/modules/ppolicy.inc:502 ../lib/modules/ppolicy.inc:505 +#: ../lib/modules/windowsUser.inc:566 ../lib/modules/windowsUser.inc:2208 +#: ../lib/modules/windowsUser.inc:2723 ../lib/modules/windowsUser.inc:2728 +#: ../lib/modules/windowsUser.inc:2734 ../lib/modules/windowsUser.inc:2739 +#: ../lib/modules/ppolicy.inc:34 ../lib/modules/ppolicy.inc:473 +#: ../lib/modules/ppolicy.inc:483 ../lib/modules/ppolicy.inc:488 +#: ../lib/modules/ppolicy.inc:495 ../lib/modules/ppolicy.inc:498 #: ../lib/modules/kopanoDynamicGroup.inc:357 -#: ../lib/modules/kopanoDynamicGroup.inc:360 ../lib/modules/kopanoUser.inc:633 -#: ../lib/modules/kopanoUser.inc:1279 ../lib/modules/kopanoUser.inc:1294 -#: ../lib/modules/kopanoUser.inc:1305 ../lib/modules/kopanoUser.inc:1311 +#: ../lib/modules/kopanoDynamicGroup.inc:360 ../lib/modules/kopanoUser.inc:595 +#: ../lib/modules/kopanoUser.inc:1198 ../lib/modules/kopanoUser.inc:1213 +#: ../lib/modules/kopanoUser.inc:1224 ../lib/modules/kopanoUser.inc:1230 #: ../lib/modules/generalInformation.inc:101 #: ../lib/modules/kopanoAddressList.inc:296 #: ../lib/modules/kopanoAddressList.inc:299 ../lib/modules/zarafaServer.inc:331 @@ -19778,11 +19805,11 @@ msgstr "" msgid "ou=accounts,dc=yourdomain,dc=org" msgstr "ou=accounts,dc=ihreFirma,dc=de" -#: ../lib/modules/posixAccount.inc:269 +#: ../lib/modules/posixAccount.inc:274 msgid "pc01$" msgstr "pc01$" -#: ../lib/modules/posixAccount.inc:2475 +#: ../lib/modules/posixAccount.inc:2433 msgid "pc01,Room 2.34" msgstr "pc01,Raum 2.34" @@ -19802,11 +19829,11 @@ msgstr "drucker01" msgid "printergroup1" msgstr "druckergruppe1" -#: ../templates/selfService/adminMain.php:543 +#: ../templates/selfService/adminMain.php:555 msgid "reCAPTCHA secret key" msgstr "reCAPTCHA geheimer Schlüssel" -#: ../templates/selfService/adminMain.php:542 +#: ../templates/selfService/adminMain.php:554 msgid "reCAPTCHA site key" msgstr "reCAPTCHA Seitenschlüssel" @@ -19822,14 +19849,14 @@ msgstr "Aktualisieren" msgid "rename" msgstr "Umbenennen" -#: ../templates/3rdParty/pla/lib/PageRender.php:393 ../lib/html.inc:918 -#: ../lib/html.inc:1991 ../lib/html.inc:2672 ../lib/html.inc:4337 -#: ../lib/html.inc:4493 +#: ../templates/3rdParty/pla/lib/PageRender.php:393 ../lib/html.inc:927 +#: ../lib/html.inc:2000 ../lib/html.inc:2681 ../lib/html.inc:4346 +#: ../lib/html.inc:4502 msgid "required" msgstr "erforderlich" -#: ../lib/modules/sambaSamAccount.inc:1508 -#: ../lib/modules/sambaSamAccount.inc:1981 +#: ../lib/modules/sambaSamAccount.inc:1569 +#: ../lib/modules/sambaSamAccount.inc:2028 msgid "reset" msgstr "zurücksetzen" @@ -19846,9 +19873,9 @@ msgid "seconds" msgstr "Sekunden" #: ../lib/modules/mitKerberos.inc:243 ../lib/modules/asteriskAccount.inc:325 -#: ../lib/modules/posixGroup.inc:463 ../lib/modules/sambaSamAccount.inc:435 -#: ../lib/modules/posixAccount.inc:2443 ../lib/modules/windowsUser.inc:420 -#: ../lib/modules/inetOrgPerson.inc:2192 +#: ../lib/modules/posixGroup.inc:412 ../lib/modules/sambaSamAccount.inc:435 +#: ../lib/modules/posixAccount.inc:2401 ../lib/modules/windowsUser.inc:420 +#: ../lib/modules/inetOrgPerson.inc:2193 #: ../lib/modules/asteriskVoicemail.inc:166 #: ../lib/modules/heimdalKerberos.inc:212 msgid "secret" @@ -19859,10 +19886,10 @@ msgid "select the rdn attribute" msgstr "RDN-Attribut wählen" #: ../lib/modules/mitKerberos.inc:212 ../lib/modules/uidObject.inc:68 -#: ../lib/modules/posixAccount.inc:207 ../lib/modules/windowsUser.inc:412 +#: ../lib/modules/posixAccount.inc:212 ../lib/modules/windowsUser.inc:412 #: ../lib/modules/windowsUser.inc:659 ../lib/modules/windowsUser.inc:668 -#: ../lib/modules/pykotaUser.inc:195 ../lib/modules/pykotaUser.inc:737 -#: ../lib/modules/inetOrgPerson.inc:2183 ../lib/modules/heimdalKerberos.inc:181 +#: ../lib/modules/pykotaUser.inc:195 ../lib/modules/pykotaUser.inc:719 +#: ../lib/modules/inetOrgPerson.inc:2184 ../lib/modules/heimdalKerberos.inc:181 #: ../lib/modules/nisMailAlias.inc:109 msgid "smiller" msgstr "hmueller" @@ -19923,16 +19950,16 @@ msgid "user01,user02,user03" msgstr "benutzer01,benutzer02,benutzer03" #: ../lib/modules/qmailUser.inc:239 ../lib/modules/qmailUser.inc:245 -#: ../lib/modules/qmailUser.inc:951 ../lib/modules/eduPerson.inc:162 +#: ../lib/modules/qmailUser.inc:791 ../lib/modules/eduPerson.inc:162 #: ../lib/modules/windowsUser.inc:498 ../lib/modules/windowsUser.inc:504 -#: ../lib/modules/pykotaUser.inc:747 ../lib/modules/inetOrgPerson.inc:389 +#: ../lib/modules/pykotaUser.inc:729 ../lib/modules/inetOrgPerson.inc:389 #: ../lib/modules/kolabSharedFolder.inc:153 #: ../lib/modules/asteriskVoicemail.inc:179 ../lib/modules/qmailGroup.inc:304 #: ../lib/modules/qmailGroup.inc:312 ../lib/modules/qmailGroup.inc:320 msgid "user@company.com" msgstr "benutzer@firma.de" -#: ../lib/modules/posixAccount.inc:221 +#: ../lib/modules/posixAccount.inc:226 msgid "users" msgstr "Benutzer" @@ -19944,10 +19971,10 @@ msgstr "Einträge anzeigen" msgid "with " msgstr "mit" -#: ../templates/config/confmain.php:193 ../templates/schema/schema.php:234 +#: ../templates/config/confmain.php:197 ../templates/schema/schema.php:234 #: ../templates/schema/schema.php:316 ../templates/schema/schema.php:339 #: ../templates/schema/schema.php:341 ../templates/schema/schema.php:343 -#: ../lib/modules/kopanoServer.inc:333 +#: ../lib/modules/kopanoServer.inc:313 #: ../lib/modules/zarafaDynamicGroup.inc:415 #: ../lib/modules/zarafaDynamicGroup.inc:422 #: ../lib/modules/zarafaContact.inc:640 ../lib/modules/zarafaGroup.inc:655 @@ -19955,27 +19982,27 @@ msgstr "mit" #: ../lib/modules/kopanoGroup.inc:606 ../lib/modules/kopanoGroup.inc:609 #: ../lib/modules/kopanoGroup.inc:616 ../lib/modules/zarafaAddressList.inc:315 #: ../lib/modules/zarafaAddressList.inc:322 -#: ../lib/modules/sambaSamAccount.inc:1892 -#: ../lib/modules/sambaSamAccount.inc:1935 -#: ../lib/modules/sambaSamAccount.inc:1943 -#: ../lib/modules/sambaSamAccount.inc:1953 -#: ../lib/modules/sambaSamAccount.inc:1958 -#: ../lib/modules/sambaSamAccount.inc:1963 ../lib/modules/zarafaUser.inc:633 +#: ../lib/modules/sambaSamAccount.inc:1939 +#: ../lib/modules/sambaSamAccount.inc:1982 +#: ../lib/modules/sambaSamAccount.inc:1990 +#: ../lib/modules/sambaSamAccount.inc:2000 +#: ../lib/modules/sambaSamAccount.inc:2005 +#: ../lib/modules/sambaSamAccount.inc:2010 ../lib/modules/zarafaUser.inc:633 #: ../lib/modules/zarafaUser.inc:1281 ../lib/modules/zarafaUser.inc:1297 #: ../lib/modules/zarafaUser.inc:1307 ../lib/modules/zarafaUser.inc:1313 -#: ../lib/modules/kopanoContact.inc:595 ../lib/modules/windowsUser.inc:536 +#: ../lib/modules/kopanoContact.inc:592 ../lib/modules/windowsUser.inc:536 #: ../lib/modules/windowsUser.inc:544 ../lib/modules/windowsUser.inc:558 #: ../lib/modules/windowsUser.inc:566 ../lib/modules/windowsUser.inc:692 -#: ../lib/modules/windowsUser.inc:2236 ../lib/modules/windowsUser.inc:2638 -#: ../lib/modules/windowsUser.inc:2753 ../lib/modules/windowsUser.inc:2758 -#: ../lib/modules/windowsUser.inc:2764 ../lib/modules/windowsUser.inc:2769 -#: ../lib/modules/ppolicy.inc:36 ../lib/modules/ppolicy.inc:492 -#: ../lib/modules/ppolicy.inc:497 ../lib/modules/ppolicy.inc:500 -#: ../lib/modules/ppolicy.inc:507 ../lib/modules/kopanoDynamicGroup.inc:355 +#: ../lib/modules/windowsUser.inc:2208 ../lib/modules/windowsUser.inc:2610 +#: ../lib/modules/windowsUser.inc:2725 ../lib/modules/windowsUser.inc:2730 +#: ../lib/modules/windowsUser.inc:2736 ../lib/modules/windowsUser.inc:2741 +#: ../lib/modules/ppolicy.inc:35 ../lib/modules/ppolicy.inc:485 +#: ../lib/modules/ppolicy.inc:490 ../lib/modules/ppolicy.inc:493 +#: ../lib/modules/ppolicy.inc:500 ../lib/modules/kopanoDynamicGroup.inc:355 #: ../lib/modules/kopanoDynamicGroup.inc:362 -#: ../lib/modules/inetOrgPerson.inc:463 ../lib/modules/kopanoUser.inc:633 -#: ../lib/modules/kopanoUser.inc:1281 ../lib/modules/kopanoUser.inc:1297 -#: ../lib/modules/kopanoUser.inc:1307 ../lib/modules/kopanoUser.inc:1313 +#: ../lib/modules/inetOrgPerson.inc:463 ../lib/modules/kopanoUser.inc:595 +#: ../lib/modules/kopanoUser.inc:1200 ../lib/modules/kopanoUser.inc:1216 +#: ../lib/modules/kopanoUser.inc:1226 ../lib/modules/kopanoUser.inc:1232 #: ../lib/modules/generalInformation.inc:103 #: ../lib/modules/kopanoAddressList.inc:294 #: ../lib/modules/kopanoAddressList.inc:301 ../lib/modules/zarafaServer.inc:333 @@ -19983,16 +20010,19 @@ msgstr "mit" msgid "yes" msgstr "ja" -#: ../lib/modules/sambaSamAccount.inc:1884 -#: ../lib/modules/sambaSamAccount.inc:2273 +#: ../lib/modules/sambaSamAccount.inc:1931 +#: ../lib/modules/sambaSamAccount.inc:2320 msgid "yes - ordered ascending" msgstr "ja - aufsteigend sortiert" -#: ../lib/modules/sambaSamAccount.inc:1885 -#: ../lib/modules/sambaSamAccount.inc:2274 +#: ../lib/modules/sambaSamAccount.inc:1932 +#: ../lib/modules/sambaSamAccount.inc:2321 msgid "yes - ordered descending" msgstr "ja - absteigend sortiert" +#~ msgid "Please enter your YubiKey ids." +#~ msgstr "Bitte geben Sie Ihre YubiKey IDs ein." + #~ msgid "Mailbox already exists on IMAP server." #~ msgstr "Das Postfach existiert bereits auf dem IMAP-Server." From 9208cb2349631b1f189d7c337e7a539d21fd0925 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Mon, 9 Dec 2019 21:35:37 +0100 Subject: [PATCH 23/51] support skipping of 2FA --- lam/lib/2factor.inc | 23 ++++++++++++++++++++++- lam/lib/config.inc | 2 +- lam/lib/webauthn.inc | 13 +++++++++++++ lam/templates/config/confmain.php | 4 ++-- lam/templates/lib/500_lam.js | 6 ++++++ 5 files changed, 44 insertions(+), 4 deletions(-) diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index 3640ffed..b3176386 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -11,6 +11,7 @@ use \htmlStatusMessage; use \htmlDiv; use \LAMException; use Webauthn\PublicKeyCredentialCreationOptions; +use function LAM\LOGIN\WEBAUTHN\hasTokensRegistered; use function LAM\LOGIN\WEBAUTHN\storeNewRegistration; /* @@ -547,6 +548,15 @@ class WebauthnProvider extends BaseProvider { $row->add($loginButton, 12); $errorMessage = new htmlStatusMessage('ERROR', '', _('This service requires a browser with "WebAuthn" support.')); $row->add(new htmlDiv(null, $errorMessage, array('hidden webauthn-error')), 12); + if ($this->config->twoFactorAuthenticationOptional === true) { + include_once __DIR__ . '/webauthn.inc'; + $hasTokens = hasTokensRegistered($userDn); + if (!$hasTokens) { + $skipButton = new htmlButton('skip_webauthn', _('Skip')); + $skipButton->setCSSClasses(array('fullwidth')); + $row->add($skipButton, 12); + } + } $row->add(new htmlJavaScript('window.lam.webauthn.start(\'' . $pathPrefix . '\');'), 0); } @@ -556,8 +566,12 @@ class WebauthnProvider extends BaseProvider { */ public function verify2ndFactor($user, $password, $serial, $twoFactorInput) { logNewMessage(LOG_DEBUG, 'WebauthnProvider: Checking 2nd factor for ' . $user); - $response = base64_decode($_POST['sig_response']); include_once __DIR__ . '/webauthn.inc'; + logNewMessage(LOG_ERR, $user); + if ($this->config->twoFactorAuthenticationOptional && !hasTokensRegistered($user) && ($_POST['sig_response'] === 'skip')) { + return true; + } + $response = base64_decode($_POST['sig_response']); $registrationObject = PublicKeyCredentialCreationOptions::createFromString($_SESSION['webauthn_registration']); if (storeNewRegistration($registrationObject, $response)) { return true; @@ -634,6 +648,7 @@ class TwoFactorProviderService { $tfConfig->isSelfService = true; $tfConfig->twoFactorAuthentication = $profile->twoFactorAuthentication; $tfConfig->twoFactorAuthenticationInsecure = $profile->twoFactorAuthenticationInsecure; + $tfConfig->twoFactorAuthenticationOptional = $profile->twoFactorAuthenticationOptional; if ($tfConfig->twoFactorAuthentication == TwoFactorProviderService::TWO_FACTOR_YUBICO) { $tfConfig->twoFactorAuthenticationURL = explode("\r\n", $profile->twoFactorAuthenticationURL); } @@ -673,6 +688,7 @@ class TwoFactorProviderService { $tfConfig->isSelfService = false; $tfConfig->twoFactorAuthentication = $conf->getTwoFactorAuthentication(); $tfConfig->twoFactorAuthenticationInsecure = $conf->getTwoFactorAuthenticationInsecure(); + $tfConfig->twoFactorAuthenticationOptional = $conf->getTwoFactorAuthenticationOptional(); if ($tfConfig->twoFactorAuthentication == TwoFactorProviderService::TWO_FACTOR_YUBICO) { $tfConfig->twoFactorAuthenticationURL = explode("\r\n", $conf->getTwoFactorAuthenticationURL()); } @@ -741,4 +757,9 @@ class TwoFactorConfiguration { */ public $twoFactorAuthenticationSerialAttributeName = null; + /** + * @var bool 2FA is optional + */ + public $twoFactorAuthenticationOptional = false; + } diff --git a/lam/lib/config.inc b/lam/lib/config.inc index ec339107..53324f34 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -2480,7 +2480,7 @@ class LAMConfig { * @return bool 2nd factor is optional */ public function getTwoFactorAuthenticationOptional() { - return $this->twoFactorAuthenticationOptional; + return boolval($this->twoFactorAuthenticationOptional); } /** diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index b0847854..cf2cb955 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -274,6 +274,19 @@ function getExtensionOutputChecker() { return new ExtensionOutputCheckerHandler(); } +/** + * Returns if there are any tokens registered for the given DN. + * + * @param string $dn user DN + * @return bool at least one token is registered + */ +function hasTokensRegistered($dn) { + $repository = new PublicKeyCredentialSourceRepositorySQLite(); + $userEntity = getUserEntity($dn); + $tokens = $repository->findAllForUserEntity($userEntity); + return !empty($tokens); +} + /** * Stores the public key credentials in the SQLite database. * diff --git a/lam/templates/config/confmain.php b/lam/templates/config/confmain.php index a99f2d5e..b80d5296 100644 --- a/lam/templates/config/confmain.php +++ b/lam/templates/config/confmain.php @@ -479,7 +479,7 @@ if (extension_loaded('curl')) { TwoFactorProviderService::TWO_FACTOR_YUBICO => array('twoFactorURL', 'twoFactorAttribute', 'twoFactorDomain'), TwoFactorProviderService::TWO_FACTOR_DUO => array('twoFactorURLs', 'twoFactorOptional', 'twoFactorInsecure', 'twoFactorLabel', 'twoFactorDomain'), TwoFactorProviderService::TWO_FACTOR_WEBAUTHN => array('twoFactorURL', 'twoFactorURLs', 'twoFactorInsecure', 'twoFactorLabel', - 'twoFactorOptional', 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey', 'twoFactorAttribute'), + 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey', 'twoFactorAttribute'), )); $twoFactorSelect->setTableRowsToShow(array( TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA => array('twoFactorURL', 'twoFactorInsecure', 'twoFactorLabel', @@ -488,7 +488,7 @@ if (extension_loaded('curl')) { 'twoFactorOptional', 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey'), TwoFactorProviderService::TWO_FACTOR_DUO => array('twoFactorURL', 'twoFactorLabel', 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey', 'twoFactorAttribute'), - TwoFactorProviderService::TWO_FACTOR_WEBAUTHN => array('twoFactorDomain') + TwoFactorProviderService::TWO_FACTOR_WEBAUTHN => array('twoFactorDomain', 'twoFactorOptional') )); $row->add($twoFactorSelect, 12); $twoFactorAttribute = new htmlResponsiveInputField(_("User name attribute"), 'twoFactorAttribute', $conf->getTwoFactorAuthenticationAttribute(), '528'); diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index b51c55ed..954b1e11 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -1377,6 +1377,12 @@ window.lam.webauthn.start = function(prefix) { * @param prefix path prefix for Ajax endpoint */ window.lam.webauthn.run = function(prefix) { + jQuery('#btn_skip_webauthn').click(function () { + let form = jQuery("#2faform"); + form.append(''); + form.submit(); + return; + }); var token = jQuery('#sec_token').val(); // check for webauthn support if (!navigator.credentials || (typeof(PublicKeyCredential) === "undefined")) { From 0e835e30037d51bed19edfa6eea558010a204020 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 19 Dec 2019 22:01:54 +0100 Subject: [PATCH 24/51] added webauthn error message --- composer.json | 6 +----- lam/lib/2factor.inc | 6 +++++- lam/lib/html.inc | 2 +- lam/lib/webauthn.inc | 3 ++- lam/templates/lib/500_lam.js | 20 +++++++++++++++++--- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index de5de7f6..8ffeb2e6 100644 --- a/composer.json +++ b/composer.json @@ -1,10 +1,6 @@ { - "require": { - "web-auth/webauthn-lib" : "2.1.7", - "symfony/http-foundation" : "5.0.0" - }, "require-dev" : { "phpunit/phpunit" : "5.7.27", "squizlabs/php_codesniffer" : "3.4.0" } -} \ No newline at end of file +} diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index b3176386..2a63bbdb 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -8,6 +8,7 @@ use \htmlImage; use \htmlButton; use \htmlJavaScript; use \htmlStatusMessage; +use \htmlOutputText; use \htmlDiv; use \LAMException; use Webauthn\PublicKeyCredentialCreationOptions; @@ -557,6 +558,10 @@ class WebauthnProvider extends BaseProvider { $row->add($skipButton, 12); } } + $errorMessageDiv = new htmlDiv('generic-webauthn-error', new htmlOutputText('')); + $errorMessageDiv->addDataAttribute('button', _('Ok')); + $errorMessageDiv->addDataAttribute('title', _('Webauthn failed')); + $row->add($errorMessageDiv, 12); $row->add(new htmlJavaScript('window.lam.webauthn.start(\'' . $pathPrefix . '\');'), 0); } @@ -567,7 +572,6 @@ class WebauthnProvider extends BaseProvider { public function verify2ndFactor($user, $password, $serial, $twoFactorInput) { logNewMessage(LOG_DEBUG, 'WebauthnProvider: Checking 2nd factor for ' . $user); include_once __DIR__ . '/webauthn.inc'; - logNewMessage(LOG_ERR, $user); if ($this->config->twoFactorAuthenticationOptional && !hasTokensRegistered($user) && ($_POST['sig_response'] === 'skip')) { return true; } diff --git a/lam/lib/html.inc b/lam/lib/html.inc index e199c5d6..14410395 100644 --- a/lam/lib/html.inc +++ b/lam/lib/html.inc @@ -3626,7 +3626,7 @@ class htmlDiv extends htmlElement { if (($this->cssClasses != null) && (sizeof($this->cssClasses) > 0)) { $classesValue = ' class="' . implode(' ', $this->cssClasses) . '"'; } - echo ''; + echo 'getDataAttributesAsString() . '>'; if ($this->content != null) { $return = $this->content->generateHTML($module, $input, $values, $restricted, $tabindex, $scope); } diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index cf2cb955..1281c74f 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -40,6 +40,7 @@ use \Webauthn\PublicKeyCredentialUserEntity; use \Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; use \Webauthn\AuthenticatorSelectionCriteria; use Webauthn\TokenBinding\IgnoreTokenBindingHandler; +use \LAMException; /* @@ -104,7 +105,7 @@ function getRegistrationObject($dn, $isSelfService) { new AuthenticatorSelectionCriteria(), PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, new AuthenticationExtensionsClientInputs()); - logNewMessage(LOG_DEBUG, json_encode($registrationObject)); + logNewMessage(LOG_DEBUG, 'Webauthn registration: ' . json_encode($registrationObject)); return $registrationObject; } diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index 954b1e11..67a27338 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -841,9 +841,14 @@ window.lam.form.autoTrim = function() { window.lam.dialog = window.lam.dialog || {}; -window.lam.dialog.showMessage = function(title, okText, divId) { +window.lam.dialog.showMessage = function(title, okText, divId, callbackFunction) { var buttonList = {}; - buttonList[okText] = function() { jQuery(this).dialog("close"); }; + buttonList[okText] = function() { + jQuery(this).dialog("close"); + if (callbackFunction) { + callbackFunction(); + } + }; jQuery('#' + divId).dialog({ modal: true, title: title, @@ -1445,7 +1450,16 @@ window.lam.webauthn.register = function(publicKey) { form.submit(); }, function (error) { console.log(error.message); - jQuery('#btn_logout').click(); + let errorDiv = jQuery('#generic-webauthn-error'); + let buttonLabel = errorDiv.data('button'); + let dialogTitle = errorDiv.data('title'); + errorDiv.text(error.message); + window.lam.dialog.showMessage(dialogTitle, + buttonLabel, + 'generic-webauthn-error', + function () { + jQuery('#btn_logout').click(); + }); }); } From 27a42346342ef54641641b75b456436c6c126741 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 21 Dec 2019 14:18:03 +0100 Subject: [PATCH 25/51] added unit tests --- lam/lib/webauthn.inc | 2 +- lam/tests/lib/webauthnTest.php | 123 +++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 lam/tests/lib/webauthnTest.php diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index 1281c74f..f672a14f 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -370,7 +370,7 @@ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSo * * @return string the PDO URL */ - protected function getPdoUrl() { + public function getPdoUrl() { $fileName = dirname(__FILE__) . '/../config/__lam.webauthn.sqlite'; if (!file_exists($fileName)) { $handle = fopen($fileName, 'w'); diff --git a/lam/tests/lib/webauthnTest.php b/lam/tests/lib/webauthnTest.php new file mode 100644 index 00000000..687f5f59 --- /dev/null +++ b/lam/tests/lib/webauthnTest.php @@ -0,0 +1,123 @@ +database = $this + ->getMockBuilder(PublicKeyCredentialSourceRepositorySQLite::class) + ->setMethods(array('getPdoUrl')) + ->getMock(); + $file = tmpfile(); + $filePath = stream_get_meta_data($file)['uri']; + $this->database->method('getPdoUrl')->willReturn('sqlite:' . $filePath); + } + + /** + * Empty DB test + */ + public function test_findOneByCredentialId_emptyDb() { + $result = $this->database->findOneByCredentialId("test"); + $this->assertNull($result); + } + + /** + * Empty DB test + */ + public function test_findAllForUserEntity_emptyDb() { + $entity = new PublicKeyCredentialUserEntity("cn=test,dc=example", "cn=test,dc=example", "test", null); + + $result = $this->database->findAllForUserEntity($entity); + $this->assertEmpty($result); + } + + /** + * Save multiple credentials and read them. + */ + public function test_saveCredentialSource() { + $source1 = new PublicKeyCredentialSource( + "id1", + PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, + array(), + "atype", + new CertificateTrustPath(array('x5c' => 'test')), + \Ramsey\Uuid\Uuid::uuid1(), + "p1", + "uh1", + 1); + $this->database->saveCredentialSource($source1); + $source2 = new PublicKeyCredentialSource( + "id2", + PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, + array(), + "atype", + new CertificateTrustPath(array('x5c' => 'test')), + \Ramsey\Uuid\Uuid::uuid1(), + "p2", + "uh1", + 1); + $this->database->saveCredentialSource($source2); + $source3 = new PublicKeyCredentialSource( + "id3", + PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, + array(), + "atype", + new CertificateTrustPath(array('x5c' => 'test')), + \Ramsey\Uuid\Uuid::uuid1(), + "p3", + "uh2", + 1); + $this->database->saveCredentialSource($source3); + + $this->assertNotNull($this->database->findOneByCredentialId("id1")); + $this->assertNotNull($this->database->findOneByCredentialId("id2")); + $this->assertNotNull($this->database->findOneByCredentialId("id3")); + $this->assertEquals(2, sizeof( + $this->database->findAllForUserEntity(new PublicKeyCredentialUserEntity("uh1", "uh1", "uh1", null)) + )); + $this->assertEquals(1, sizeof( + $this->database->findAllForUserEntity(new PublicKeyCredentialUserEntity("uh2", "uh2", "uh2", null)) + )); + } + +} + From de1977021173c06743220546aa50ca5ebdefc78f Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 21 Dec 2019 15:08:48 +0100 Subject: [PATCH 26/51] refactoring --- lam/lib/2factor.inc | 7 +- lam/lib/webauthn.inc | 216 +++++++++++++++++++----------------- lam/templates/misc/ajax.php | 8 +- 3 files changed, 121 insertions(+), 110 deletions(-) diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index 2a63bbdb..560067e3 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -1,5 +1,6 @@ add(new htmlDiv(null, $errorMessage, array('hidden webauthn-error')), 12); if ($this->config->twoFactorAuthenticationOptional === true) { include_once __DIR__ . '/webauthn.inc'; - $hasTokens = hasTokensRegistered($userDn); + $webauthnManager = new WebauthnManager(); + $hasTokens = $webauthnManager->isRegistered($userDn); if (!$hasTokens) { $skipButton = new htmlButton('skip_webauthn', _('Skip')); $skipButton->setCSSClasses(array('fullwidth')); @@ -572,7 +574,8 @@ class WebauthnProvider extends BaseProvider { public function verify2ndFactor($user, $password, $serial, $twoFactorInput) { logNewMessage(LOG_DEBUG, 'WebauthnProvider: Checking 2nd factor for ' . $user); include_once __DIR__ . '/webauthn.inc'; - if ($this->config->twoFactorAuthenticationOptional && !hasTokensRegistered($user) && ($_POST['sig_response'] === 'skip')) { + $webauthnManager = new WebauthnManager(); + if ($this->config->twoFactorAuthenticationOptional && !$webauthnManager->isRegistered($user) && ($_POST['sig_response'] === 'skip')) { return true; } $response = base64_decode($_POST['sig_response']); diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index f672a14f..81753416 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -72,103 +72,124 @@ use \LAMException; include_once __DIR__ . '/3rdParty/composer/autoload.php'; /** - * Returns if the given DN is registered for webauthn. + * Manages Webauthn registrations and authentications. * - * @param string $dn DN - * @return boolean is registered + * @package LAM\LOGIN\WEBAUTHN */ -function isRegistered($dn) { - return false; -} +class WebauthnManager { -/** - * Returns a challenge for a new token. - * - * @param string $dn DN - * @param bool $isSelfService is executed in self service - * @return PublicKeyCredentialCreationOptions registration object - */ -function getRegistrationObject($dn, $isSelfService) { - $rpEntity = createRpEntry($isSelfService); - $userEntity = getUserEntity($dn); - $challenge = generateRandomPassword(32); - $credentialParameters = getCredentialParameters(); - $excludedKeys = getExcludedKeys($userEntity); - $timeout = 20000; - $registrationObject = new PublicKeyCredentialCreationOptions( - $rpEntity, - $userEntity, - $challenge, - $credentialParameters, - $timeout, - $excludedKeys, - new AuthenticatorSelectionCriteria(), - PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, - new AuthenticationExtensionsClientInputs()); - logNewMessage(LOG_DEBUG, 'Webauthn registration: ' . json_encode($registrationObject)); - return $registrationObject; -} - -/** - * Returns the part that identifies the server and application. - * - * @param bool $isSelfService is executed in self service - * @return PublicKeyCredentialRpEntity relying party entry - */ -function createRpEntry($isSelfService) { - $pathPrefix = $isSelfService ? '../' : ''; - $icon = $pathPrefix . '../graphics/logo136.png'; - if (!$isSelfService) { - $domain = $_SESSION['config']->getTwoFactorAuthenticationDomain(); + /** + * Returns if the given DN is registered for webauthn. + * + * @param string $dn DN + * @return boolean is registered + */ + public function isRegistered($dn) { + $database = $this->getDatabase(); + $userEntity = $this->getUserEntity($dn); + $results = $database->findAllForUserEntity($userEntity); + return !empty($results); } - return new PublicKeyCredentialRpEntity( - 'LDAP Account Manager', //Name - $domain, - $icon - ); -} -/** - * Returns the user entity for the registration. - * - * @param $dn DN - * @return PublicKeyCredentialUserEntity user entity - */ -function getUserEntity($dn) { - return new PublicKeyCredentialUserEntity( - $dn, - $dn, - extractRDNValue($dn), - null - ); -} - -/** - * Returns the supported credential algorithms. - * - * @return array algorithms - */ -function getCredentialParameters() { - return array( - new PublicKeyCredentialParameters('public-key', Algorithms::COSE_ALGORITHM_ES256), - new PublicKeyCredentialParameters('public-key', Algorithms::COSE_ALGORITHM_RS256), - ); -} - -/** - * Returns a list of all credential ids that are already registered. - * - * @param PublicKeyCredentialUserEntity $user user data - * @return PublicKeyCredentialDescriptor[] credential ids - */ -function getExcludedKeys($user) { - $keys = array(); - $repository = new PublicKeyCredentialSourceRepositorySQLite(); - $credentialSources = $repository->findAllForUserEntity($user); - foreach ($credentialSources as $credentialSource) { - $keys[] = new PublicKeyCredentialDescriptor(PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, $credentialSource->getPublicKeyCredentialId()); + /** + * Returns a challenge for a new token. + * + * @param string $dn DN + * @param bool $isSelfService is executed in self service + * @return PublicKeyCredentialCreationOptions registration object + */ + public function getRegistrationObject($dn, $isSelfService) { + $rpEntity = $this->createRpEntry($isSelfService); + $userEntity = $this->getUserEntity($dn); + $challenge = generateRandomPassword(32); + $credentialParameters = $this->getCredentialParameters(); + $excludedKeys = $this->getExcludedKeys($userEntity); + $timeout = 20000; + $registrationObject = new PublicKeyCredentialCreationOptions( + $rpEntity, + $userEntity, + $challenge, + $credentialParameters, + $timeout, + $excludedKeys, + new AuthenticatorSelectionCriteria(), + PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, + new AuthenticationExtensionsClientInputs()); + logNewMessage(LOG_DEBUG, 'Webauthn registration: ' . json_encode($registrationObject)); + return $registrationObject; } - return $keys; + + /** + * Returns the user entity for the registration. + * + * @param $dn DN + * @return PublicKeyCredentialUserEntity user entity + */ + private function getUserEntity($dn) { + return new PublicKeyCredentialUserEntity( + $dn, + $dn, + extractRDNValue($dn), + null + ); + } + + /** + * Returns the part that identifies the server and application. + * + * @param bool $isSelfService is executed in self service + * @return PublicKeyCredentialRpEntity relying party entry + */ + private function createRpEntry($isSelfService) { + $pathPrefix = $isSelfService ? '../' : ''; + $icon = $pathPrefix . '../graphics/logo136.png'; + if (!$isSelfService) { + $domain = $_SESSION['config']->getTwoFactorAuthenticationDomain(); + } + return new PublicKeyCredentialRpEntity( + 'LDAP Account Manager', //Name + $domain, + $icon + ); + } + + /** + * Returns the supported credential algorithms. + * + * @return array algorithms + */ + private function getCredentialParameters() { + return array( + new PublicKeyCredentialParameters('public-key', Algorithms::COSE_ALGORITHM_ES256), + new PublicKeyCredentialParameters('public-key', Algorithms::COSE_ALGORITHM_RS256), + ); + } + + /** + * Returns a list of all credential ids that are already registered. + * + * @param PublicKeyCredentialUserEntity $user user data + * @return PublicKeyCredentialDescriptor[] credential ids + */ + private function getExcludedKeys($user) { + $keys = array(); + $repository = $this->getDatabase(); + $credentialSources = $repository->findAllForUserEntity($user); + foreach ($credentialSources as $credentialSource) { + $keys[] = new PublicKeyCredentialDescriptor(PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, $credentialSource->getPublicKeyCredentialId()); + } + return $keys; + } + + /** + * Returns the webauthn database. + * + * @return PublicKeyCredentialSourceRepositorySQLite database + */ + public function getDatabase() { + return new PublicKeyCredentialSourceRepositorySQLite(); + } + } /** @@ -275,19 +296,6 @@ function getExtensionOutputChecker() { return new ExtensionOutputCheckerHandler(); } -/** - * Returns if there are any tokens registered for the given DN. - * - * @param string $dn user DN - * @return bool at least one token is registered - */ -function hasTokensRegistered($dn) { - $repository = new PublicKeyCredentialSourceRepositorySQLite(); - $userEntity = getUserEntity($dn); - $tokens = $repository->findAllForUserEntity($userEntity); - return !empty($tokens); -} - /** * Stores the public key credentials in the SQLite database. * diff --git a/lam/templates/misc/ajax.php b/lam/templates/misc/ajax.php index 2db6062c..572871a8 100644 --- a/lam/templates/misc/ajax.php +++ b/lam/templates/misc/ajax.php @@ -7,8 +7,7 @@ use \htmlResponsiveRow; use \htmlLink; use \htmlOutputText; use \htmlButton; -use function LAM\LOGIN\WEBAUTHN\getRegistrationObject; -use function LAM\LOGIN\WEBAUTHN\isRegistered; +use LAM\LOGIN\WEBAUTHN\WebauthnManager; /* @@ -193,9 +192,10 @@ class Ajax { private function manageWebauthn($isSelfService) { include_once __DIR__ . '/../../lib/webauthn.inc'; $userDN = $_SESSION['ldap']->getUserName(); - $isRegistered = isRegistered($userDN); + $webauthnManager = new WebauthnManager(); + $isRegistered = $webauthnManager->isRegistered($userDN); if (!$isRegistered) { - $registrationObject = getRegistrationObject($userDN, $isSelfService); + $registrationObject = $webauthnManager->getRegistrationObject($userDN, $isSelfService); $_SESSION['webauthn_registration'] = json_encode($registrationObject); echo json_encode( array( From eae502c629b8082c5a50410c50cf784c1ba3850f Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 21 Dec 2019 15:13:48 +0100 Subject: [PATCH 27/51] refactoring --- lam/lib/2factor.inc | 4 +- lam/lib/webauthn.inc | 208 +++++++++++++++++++++---------------------- 2 files changed, 105 insertions(+), 107 deletions(-) diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index 560067e3..d4499583 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -13,8 +13,6 @@ use \htmlOutputText; use \htmlDiv; use \LAMException; use Webauthn\PublicKeyCredentialCreationOptions; -use function LAM\LOGIN\WEBAUTHN\hasTokensRegistered; -use function LAM\LOGIN\WEBAUTHN\storeNewRegistration; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) @@ -580,7 +578,7 @@ class WebauthnProvider extends BaseProvider { } $response = base64_decode($_POST['sig_response']); $registrationObject = PublicKeyCredentialCreationOptions::createFromString($_SESSION['webauthn_registration']); - if (storeNewRegistration($registrationObject, $response)) { + if ($webauthnManager->storeNewRegistration($registrationObject, $response)) { return true; } logNewMessage(LOG_ERR, 'Webauthn authentication failed'); diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index 81753416..0c79fd3a 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -119,6 +119,44 @@ class WebauthnManager { return $registrationObject; } + /** + * Verifies the registration and stores it in the database. + * + * @param PublicKeyCredentialCreationOptions $registration registration object + * @param string $clientResponse client response + * @return bool true if response is valid and registration succeeded + */ + public function storeNewRegistration($registration, $clientResponse) { + $decoder = $this->getCborDecoder(); + $tokenBindingHandler = new IgnoreTokenBindingHandler(); + $attestationSupportManager = $this->getAttestationSupportManager($decoder); + $attestationObjectLoader = $this->getAttestationObjectLoader($attestationSupportManager, $decoder); + $publicKeyCredentialLoader = $this->getPublicKeyCredentialLoader($attestationObjectLoader, $decoder); + $extensionOutputCheckerHandler = $this->getExtensionOutputChecker(); + $repository = new PublicKeyCredentialSourceRepositorySQLite(); + $responseValidator = new AuthenticatorAttestationResponseValidator( + $attestationSupportManager, $repository, $tokenBindingHandler, $extensionOutputCheckerHandler); + try { + $publicKeyCredential = $publicKeyCredentialLoader->load($clientResponse); + $authenticatorAttestationResponse = $publicKeyCredential->getResponse(); + if (!$authenticatorAttestationResponse instanceof AuthenticatorAttestationResponse) { + logNewMessage(LOG_ERR, 'Invalid webauthn response: ' . $clientResponse); + return false; + } + $symfonyRequest = Request::createFromGlobals(); + $psr17Factory = new Psr17Factory(); + $psrFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + $psr7Request = $psrFactory->createRequest($symfonyRequest); + $publicKeyCredentialSource = $responseValidator->check($authenticatorAttestationResponse, $registration, $psr7Request); + $repository->saveCredentialSource($publicKeyCredentialSource); + return true; + } + catch (\Throwable $exception) { + logNewMessage(LOG_ERR, 'Webauthn validation failed: ' . $exception->getMessage() . $exception->getTraceAsString()); + } + return false; + } + /** * Returns the user entity for the registration. * @@ -181,6 +219,72 @@ class WebauthnManager { return $keys; } + /** + * Returns a CBOR decoder. + * + * @return Decoder decoder + */ + private function getCborDecoder() { + return new Decoder(new TagObjectManager(), new OtherObjectManager()); + } + + /** + * Creates the attestation support manager. + * + * @param Decoder $decoder decoder + * @return AttestationStatementSupportManager manager + */ + private function getAttestationSupportManager($decoder) { + $manager = new AttestationStatementSupportManager(); + $manager->add(new NoneAttestationStatementSupport()); + $manager->add(new FidoU2FAttestationStatementSupport()); + $manager->add(new AndroidKeyAttestationStatementSupport($decoder)); + $manager->add(new TPMAttestationStatementSupport()); + $coseManager = new Manager(); + $coseManager->add(new ES256()); + $coseManager->add(new ES384()); + $coseManager->add(new ES512()); + $coseManager->add(new EdDSA()); + $coseManager->add(new RS1()); + $coseManager->add(new RS256()); + $coseManager->add(new RS384); + $coseManager->add(new RS512()); + $manager->add(new PackedAttestationStatementSupport($decoder, $coseManager)); + return $manager; + } + + /** + * Returns the attestation object loader. + * + * @param AttestationStatementSupportManager $manager support manager + * @param Decoder $decoder decoder + * @return AttestationObjectLoader attestation object loader + */ + private function getAttestationObjectLoader($manager, $decoder) { + return new AttestationObjectLoader($manager, $decoder); + } + + /** + * Creates the public key credential loader. + * + * @param AttestationObjectLoader $attestationObjectLoader attestation object loader + * @param Decoder $decoder decoder + * @return PublicKeyCredentialLoader public key credential loader + */ + private function getPublicKeyCredentialLoader($attestationObjectLoader, $decoder) { + return new PublicKeyCredentialLoader($attestationObjectLoader, $decoder); + } + + /** + * Returns the extension output checker handler. + * No extensions are checked at this time. + * + * @return ExtensionOutputCheckerHandler handler + */ + function getExtensionOutputChecker() { + return new ExtensionOutputCheckerHandler(); + } + /** * Returns the webauthn database. * @@ -192,110 +296,6 @@ class WebauthnManager { } -/** - * Verifies the registration and stores it in the database. - * - * @param PublicKeyCredentialCreationOptions $registration registration object - * @param string $clientResponse client response - * @return bool true if response is valid and registration succeeded - */ -function storeNewRegistration($registration, $clientResponse) { - $decoder = getCborDecoder(); - $tokenBindingHandler = new IgnoreTokenBindingHandler(); - $attestationSupportManager = getAttestationSupportManager($decoder); - $attestationObjectLoader = getAttestationObjectLoader($attestationSupportManager, $decoder); - $publicKeyCredentialLoader = getPublicKeyCredentialLoader($attestationObjectLoader, $decoder); - $extensionOutputCheckerHandler = getExtensionOutputChecker(); - $repository = new PublicKeyCredentialSourceRepositorySQLite(); - $responseValidator = new AuthenticatorAttestationResponseValidator( - $attestationSupportManager, $repository, $tokenBindingHandler, $extensionOutputCheckerHandler); - try { - $publicKeyCredential = $publicKeyCredentialLoader->load($clientResponse); - $authenticatorAttestationResponse = $publicKeyCredential->getResponse(); - if (!$authenticatorAttestationResponse instanceof AuthenticatorAttestationResponse) { - logNewMessage(LOG_ERR, 'Invalid webauthn response: ' . $clientResponse); - return false; - } - $symfonyRequest = Request::createFromGlobals(); - $psr17Factory = new Psr17Factory(); - $psrFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); - $psr7Request = $psrFactory->createRequest($symfonyRequest); - $publicKeyCredentialSource = $responseValidator->check($authenticatorAttestationResponse, $registration, $psr7Request); - $repository->saveCredentialSource($publicKeyCredentialSource); - return true; - } - catch (\Throwable $exception) { - logNewMessage(LOG_ERR, 'Webauthn validation failed: ' . $exception->getMessage() . $exception->getTraceAsString()); - } - return false; -} - -/** - * Returns a CBOR decoder. - * - * @return Decoder decoder - */ -function getCborDecoder() { - return new Decoder(new TagObjectManager(), new OtherObjectManager()); -} - -/** - * Creates the attestation support manager. - * - * @param Decoder $decoder decoder - * @return AttestationStatementSupportManager manager - */ -function getAttestationSupportManager($decoder) { - $manager = new AttestationStatementSupportManager(); - $manager->add(new NoneAttestationStatementSupport()); - $manager->add(new FidoU2FAttestationStatementSupport()); - $manager->add(new AndroidKeyAttestationStatementSupport($decoder)); - $manager->add(new TPMAttestationStatementSupport()); - $coseManager = new Manager(); - $coseManager->add(new ES256()); - $coseManager->add(new ES384()); - $coseManager->add(new ES512()); - $coseManager->add(new EdDSA()); - $coseManager->add(new RS1()); - $coseManager->add(new RS256()); - $coseManager->add(new RS384); - $coseManager->add(new RS512()); - $manager->add(new PackedAttestationStatementSupport($decoder, $coseManager)); - return $manager; -} - -/** - * Returns the attestation object loader. - * - * @param AttestationStatementSupportManager $manager support manager - * @param Decoder $decoder decoder - * @return AttestationObjectLoader attestation object loader - */ -function getAttestationObjectLoader($manager, $decoder) { - return new AttestationObjectLoader($manager, $decoder); -} - -/** - * Creates the public key credential loader. - * - * @param AttestationObjectLoader $attestationObjectLoader attestation object loader - * @param Decoder $decoder decoder - * @return PublicKeyCredentialLoader public key credential loader - */ -function getPublicKeyCredentialLoader($attestationObjectLoader, $decoder) { - return new PublicKeyCredentialLoader($attestationObjectLoader, $decoder); -} - -/** - * Returns the extension output checker handler. - * No extensions are checked at this time. - * - * @return ExtensionOutputCheckerHandler handler - */ -function getExtensionOutputChecker() { - return new ExtensionOutputCheckerHandler(); -} - /** * Stores the public key credentials in the SQLite database. * From 4e892e2171068569d89dc3914b581cf1a6a9953a Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 21 Dec 2019 19:40:29 +0100 Subject: [PATCH 28/51] refactoring --- lam/lib/webauthn.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index 0c79fd3a..ed3b63d9 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -281,7 +281,7 @@ class WebauthnManager { * * @return ExtensionOutputCheckerHandler handler */ - function getExtensionOutputChecker() { + private function getExtensionOutputChecker() { return new ExtensionOutputCheckerHandler(); } From 7a096cfc94df89a49dc401a926bbc34035b0c3bb Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Tue, 31 Dec 2019 17:01:51 +0100 Subject: [PATCH 29/51] webauthn --- lam/lib/2factor.inc | 25 ++++-- lam/lib/webauthn.inc | 158 ++++++++++++++++++++++++++++++++--- lam/templates/lib/500_lam.js | 51 ++++++++++- lam/templates/misc/ajax.php | 13 ++- 4 files changed, 228 insertions(+), 19 deletions(-) diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index d4499583..e3d26062 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -573,13 +573,26 @@ class WebauthnProvider extends BaseProvider { logNewMessage(LOG_DEBUG, 'WebauthnProvider: Checking 2nd factor for ' . $user); include_once __DIR__ . '/webauthn.inc'; $webauthnManager = new WebauthnManager(); - if ($this->config->twoFactorAuthenticationOptional && !$webauthnManager->isRegistered($user) && ($_POST['sig_response'] === 'skip')) { - return true; + if (!empty($_SESSION['ldap'])) { + $userDn = $_SESSION['ldap']->getUserName(); } - $response = base64_decode($_POST['sig_response']); - $registrationObject = PublicKeyCredentialCreationOptions::createFromString($_SESSION['webauthn_registration']); - if ($webauthnManager->storeNewRegistration($registrationObject, $response)) { - return true; + else { + $userDn = $_SESSION['selfService_clientDN']; + } + $hasTokens = $webauthnManager->isRegistered($userDn); + if (!$hasTokens) { + if ($this->config->twoFactorAuthenticationOptional && !$webauthnManager->isRegistered($user) && ($_POST['sig_response'] === 'skip')) { + logNewMessage(LOG_DEBUG, 'Skipped 2FA for ' . $user . ' as no devices are registered and 2FA is optional.'); + return true; + } + $response = base64_decode($_POST['sig_response']); + $registrationObject = PublicKeyCredentialCreationOptions::createFromString($_SESSION['webauthn_registration']); + return $webauthnManager->storeNewRegistration($registrationObject, $response); + } + else { + logNewMessage(LOG_DEBUG, 'Checking webauthn response of ' . $userDn); + $response = base64_decode($_POST['sig_response']); + return $webauthnManager->isValidAuthentication($response, $userDn); } logNewMessage(LOG_ERR, 'Webauthn authentication failed'); return false; diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index ed3b63d9..2fa54a44 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -19,6 +19,7 @@ use Nyholm\Psr7\Factory\Psr17Factory; use PDO; use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; use Symfony\Component\HttpFoundation\Request; +use Throwable; use Webauthn\AttestationStatement\AndroidKeyAttestationStatementSupport; use Webauthn\AttestationStatement\AttestationObjectLoader; use Webauthn\AttestationStatement\AttestationStatementSupportManager; @@ -27,11 +28,14 @@ use Webauthn\AttestationStatement\NoneAttestationStatementSupport; use Webauthn\AttestationStatement\PackedAttestationStatementSupport; use Webauthn\AttestationStatement\TPMAttestationStatementSupport; use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler; +use Webauthn\AuthenticatorAssertionResponse; +use Webauthn\AuthenticatorAssertionResponseValidator; use Webauthn\AuthenticatorAttestationResponse; use Webauthn\AuthenticatorAttestationResponseValidator; use \Webauthn\PublicKeyCredentialCreationOptions; use Webauthn\PublicKeyCredentialDescriptor; use Webauthn\PublicKeyCredentialLoader; +use Webauthn\PublicKeyCredentialRequestOptions; use \Webauthn\PublicKeyCredentialRpEntity; use \Webauthn\PublicKeyCredentialParameters; use Webauthn\PublicKeyCredentialSource; @@ -101,10 +105,10 @@ class WebauthnManager { public function getRegistrationObject($dn, $isSelfService) { $rpEntity = $this->createRpEntry($isSelfService); $userEntity = $this->getUserEntity($dn); - $challenge = generateRandomPassword(32); + $challenge = $this->createChallenge(); $credentialParameters = $this->getCredentialParameters(); $excludedKeys = $this->getExcludedKeys($userEntity); - $timeout = 20000; + $timeout = $this->getTimeout(); $registrationObject = new PublicKeyCredentialCreationOptions( $rpEntity, $userEntity, @@ -240,6 +244,17 @@ class WebauthnManager { $manager->add(new FidoU2FAttestationStatementSupport()); $manager->add(new AndroidKeyAttestationStatementSupport($decoder)); $manager->add(new TPMAttestationStatementSupport()); + $coseManager = $this->getAlgorithmManager(); + $manager->add(new PackedAttestationStatementSupport($decoder, $coseManager)); + return $manager; + } + + /** + * Returns the COSE algorithm manager. + * + * @return Manager algorithm manager + */ + private function getAlgorithmManager() { $coseManager = new Manager(); $coseManager->add(new ES256()); $coseManager->add(new ES384()); @@ -249,8 +264,7 @@ class WebauthnManager { $coseManager->add(new RS256()); $coseManager->add(new RS384); $coseManager->add(new RS512()); - $manager->add(new PackedAttestationStatementSupport($decoder, $coseManager)); - return $manager; + return $coseManager; } /** @@ -294,6 +308,104 @@ class WebauthnManager { return new PublicKeyCredentialSourceRepositorySQLite(); } + /** + * Returns the timeout for user operations. + * + * @return int timeout in ms + */ + private function getTimeout() { + return 120000; + } + + /** + * Creates a new challenge. + * + * @return String challenge + */ + private function createChallenge() { + return generateRandomPassword(32); + } + + /** + * Returns the authentication object for a registered user. + * + * @param $userDN user DN + * @param bool $isSelfService self service + * @return PublicKeyCredentialRequestOptions authentication object + */ + public function getAuthenticationObject($userDN, bool $isSelfService) { + $timeout = $this->getTimeout(); + $challenge = $this->createChallenge(); + $database = $this->getDatabase(); + $userEntity = $this->getUserEntity($userDN); + $publicKeyCredentialSources = $database->findAllForUserEntity($userEntity); + $publicKeyDescriptors = array(); + foreach ($publicKeyCredentialSources as $publicKeyCredentialSource) { + $publicKeyDescriptors[] = $publicKeyCredentialSource->getPublicKeyCredentialDescriptor(); + } + $userVerification = PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_DISCOURAGED; + $extensions = new AuthenticationExtensionsClientInputs(); + $relyingParty = $this->createRpEntry($isSelfService); + return new PublicKeyCredentialRequestOptions( + $challenge, + $timeout, + $relyingParty->getId(), + $publicKeyDescriptors, + $userVerification, + $extensions + ); + } + + /** + * Checks if the provided authentication is valid. + * + * @param string $response authentication response + * @param string $userDn user DN + * @return bool true if all is ok + */ + public function isValidAuthentication(string $response, string $userDn) { + $database = $this->getDatabase(); + $decoder = $this->getCborDecoder(); + $tokenBindingHandler = new IgnoreTokenBindingHandler(); + $extensionOutputCheckerHandler = $this->getExtensionOutputChecker(); + $algorithmManager = $this->getAlgorithmManager(); + $responseValidator = new AuthenticatorAssertionResponseValidator( + $database, + $decoder, + $tokenBindingHandler, + $extensionOutputCheckerHandler, + $algorithmManager + ); + $attestationSupportManager = $this->getAttestationSupportManager($decoder); + $attestationObjectLoader = $this->getAttestationObjectLoader($attestationSupportManager, $decoder); + $publicKeyCredentialLoader = $this->getPublicKeyCredentialLoader($attestationObjectLoader, $decoder); + $publicKeyCredential = $publicKeyCredentialLoader->load($response); + $authenticatorAssertionResponse = $publicKeyCredential->getResponse(); + if (!$authenticatorAssertionResponse instanceof AuthenticatorAssertionResponse) { + logNewMessage(LOG_ERR, 'Invalid authenticator assertion response'); + return false; + } + try { + $symfonyRequest = Request::createFromGlobals(); + $psr17Factory = new Psr17Factory(); + $psrFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + $psr7Request = $psrFactory->createRequest($symfonyRequest); + $publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::createFromString($_SESSION['webauthn_authentication']); + $responseValidator->check( + $publicKeyCredential->getRawId(), + $publicKeyCredential->getResponse(), + $publicKeyCredentialRequestOptions, + $psr7Request, + $userDn + ); + return true; + } + catch (Throwable $e) { + logNewMessage(LOG_ERR, 'Error validating webauthn authentication: ' . $e->getMessage()); + } + return false; + } + } /** @@ -361,16 +473,39 @@ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSo $json = json_encode($publicKeyCredentialSource); $credentialId = base64_encode($publicKeyCredentialSource->getPublicKeyCredentialId()); $userId = $publicKeyCredentialSource->getUserHandle(); - $registrationTime = time(); + $currentTime = time(); $pdo = $this->getPDO(); - $statement = $pdo->prepare('insert into ' . self::TABLE_NAME . ' (userId, credentialId, credentialSource, registrationTime) VALUES (?, ?, ?, ?)'); + $statement = $pdo->prepare('select * from ' . self::TABLE_NAME . ' where userId = :userId and credentialId = :credentialId'); $statement->execute(array( - $userId, - $credentialId, - $json, - $registrationTime + ':userId' => $userId, + ':credentialId' => $credentialId )); - logNewMessage(LOG_DEBUG, 'Stored new credential for ' . $userId); + $results = $statement->fetchAll(); + if (empty($results)) { + $statement = $pdo->prepare('insert into ' . self::TABLE_NAME . ' (userId, credentialId, credentialSource, registrationTime, lastUseTime) VALUES (?, ?, ?, ?, ?)'); + $statement->execute(array( + $userId, + $credentialId, + $json, + $currentTime, + $currentTime + )); + logNewMessage(LOG_DEBUG, 'Stored new credential for ' . $userId); + } + else { + $statement = $pdo->prepare( + 'update ' . self::TABLE_NAME . + ' set credentialSource = :credentialSource, lastUseTime = :lastUseTime' . + ' WHERE userId = :userId AND credentialId = :credentialId' + ); + $statement->execute(array( + ':credentialSource' => $json, + ':lastUseTime' => $currentTime, + ':userId' => $userId, + ':credentialId' => $credentialId + )); + logNewMessage(LOG_DEBUG, 'Stored updated credential for ' . $userId); + } } /** @@ -432,6 +567,7 @@ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSo . 'credentialId TEXT NOT NULL,' . 'credentialSource TEXT NOT NULL,' . 'registrationTime VARCHAR(11) NOT NULL,' + . 'lastUseTime VARCHAR(11) NOT NULL,' . 'PRIMARY KEY(userId,credentialId)' . ');'; $pdo->exec($sql); diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index 67a27338..871ddb84 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -1409,6 +1409,9 @@ window.lam.webauthn.run = function(prefix) { if (jsonData.action === 'register') { window.lam.webauthn.register(jsonData.registration); } + else if (jsonData.action === 'authenticate') { + window.lam.webauthn.authenticate(jsonData.authentication); + } }) .fail(function() { console.log('Webauthn failed'); @@ -1425,7 +1428,7 @@ window.lam.webauthn.register = function(publicKey) { publicKey.user.id = Uint8Array.from(window.atob(publicKey.user.id), c=>c.charCodeAt(0)); publicKey.rp.icon = window.location.href.substring(0, window.location.href.lastIndexOf("/")) + publicKey.rp.icon; if (publicKey.excludeCredentials) { - for (var i = 0; i < publicKey.excludeCredentials.length; i++) { + for (let i = 0; i < publicKey.excludeCredentials.length; i++) { let idOrig = publicKey.excludeCredentials[i]['id']; idOrig = idOrig.replace(/-/g, "+").replace(/_/g, "/"); let idOrigDecoded = atob(idOrig); @@ -1449,6 +1452,52 @@ window.lam.webauthn.register = function(publicKey) { form.append(''); form.submit(); }, function (error) { + console.log(error.message); + let errorDiv = jQuery('#generic-webauthn-error'); + let buttonLabel = errorDiv.data('button'); + let dialogTitle = errorDiv.data('title'); + errorDiv.text(error.message); + window.lam.dialog.showMessage(dialogTitle, + buttonLabel, + 'generic-webauthn-error', + function () { + jQuery('#btn_logout').click(); + }); + }); +} + +/** + * Performs a webauthn authentication. + * + * @param publicKey authentication object + */ +window.lam.webauthn.authenticate = function(publicKey) { + publicKey.challenge = Uint8Array.from(window.atob(publicKey.challenge), c => c.charCodeAt(0)); + for (let i = 0; i < publicKey.allowCredentials.length; i++) { + let idOrig = publicKey.allowCredentials[i]['id']; + idOrig = idOrig.replace(/-/g, "+").replace(/_/g, "/"); + let idOrigDecoded = atob(idOrig); + let idArray = Uint8Array.from(idOrigDecoded, c => c.charCodeAt(0)) + publicKey.allowCredentials[i]['id'] = idArray; + } + navigator.credentials.get({publicKey}) + .then(data => { + let publicKeyCredential = { + id: data.id, + type: data.type, + rawId: window.lam.webauthn.arrayToBase64String(new Uint8Array(data.rawId)), + response: { + authenticatorData: window.lam.webauthn.arrayToBase64String(new Uint8Array(data.response.authenticatorData)), + clientDataJSON: window.lam.webauthn.arrayToBase64String(new Uint8Array(data.response.clientDataJSON)), + signature: window.lam.webauthn.arrayToBase64String(new Uint8Array(data.response.signature)), + userHandle: data.response.userHandle ? window.lam.webauthn.arrayToBase64String(new Uint8Array(data.response.userHandle)) : null + } + }; + let form = jQuery("#2faform"); + let response = btoa(JSON.stringify(publicKeyCredential)); + form.append(''); + form.submit(); + }, error => { console.log(error.message); let errorDiv = jQuery('#generic-webauthn-error'); let buttonLabel = errorDiv.data('button'); diff --git a/lam/templates/misc/ajax.php b/lam/templates/misc/ajax.php index 572871a8..61d1cb62 100644 --- a/lam/templates/misc/ajax.php +++ b/lam/templates/misc/ajax.php @@ -204,8 +204,19 @@ class Ajax { ), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); - die(); } + else { + $authenticationObject = $webauthnManager->getAuthenticationObject($userDN, $isSelfService); + $_SESSION['webauthn_authentication'] = json_encode($authenticationObject); + echo json_encode( + array( + 'action' => 'authenticate', + 'authentication' => $authenticationObject + ), + JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE + ); + } + die(); } /** From 68f6f3eafeba38fb504e87c98f008098d52553d7 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Wed, 1 Jan 2020 13:58:51 +0100 Subject: [PATCH 30/51] i18n --- lam/locale/de_DE/LC_MESSAGES/messages.mo | Bin 348858 -> 348852 bytes lam/locale/de_DE/LC_MESSAGES/messages.po | 10 +++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lam/locale/de_DE/LC_MESSAGES/messages.mo b/lam/locale/de_DE/LC_MESSAGES/messages.mo index d7da3d64352b36f45b6be3b66a9aac55d194d4b9..3604a17d9292fbec15dc27eca9b67252bca40797 100644 GIT binary patch delta 26824 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 Xq)RmeJ1*4>7BAuMr5PSv_w)Y&v;9JD delta 26845 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 QqKf~2%Z?;N1M9B*A6R}w3;+NC diff --git a/lam/locale/de_DE/LC_MESSAGES/messages.po b/lam/locale/de_DE/LC_MESSAGES/messages.po index 76ff51fc..5a657aa8 100644 --- a/lam/locale/de_DE/LC_MESSAGES/messages.po +++ b/lam/locale/de_DE/LC_MESSAGES/messages.po @@ -5,13 +5,13 @@ # # LDAP Account Manager # -# Roland Gruber , 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019. +# Roland Gruber , 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020. msgid "" msgstr "" "Project-Id-Version: messages\n" "Report-Msgid-Bugs-To: post@rolandgruber.de \n" "POT-Creation-Date: 2004-01-14 17:45+0200\n" -"PO-Revision-Date: 2019-12-07 12:58+0100\n" +"PO-Revision-Date: 2020-01-01 13:58+0100\n" "Last-Translator: Roland Gruber \n" "Language-Team: German \n" "Language: de\n" @@ -635,7 +635,7 @@ msgstr "Accountname:" #: ../lib/modules/qmailUser.inc:249 ../lib/modules/qmailUser.inc:359 #: ../lib/modules/qmailUser.inc:477 ../lib/modules/qmailUser.inc:948 msgid "Account status" -msgstr "Accountstatus" +msgstr "Kontostatus" #: ../templates/upload/masscreate.php:168 #: ../lib/modules/asteriskAccount.inc:118 @@ -2558,7 +2558,7 @@ msgstr "Windows-Passwort ändern" #: ../lib/types/user.inc:357 msgid "Change account status" -msgstr "Accountstatus ändern" +msgstr "Kontostatus ändern" #: ../templates/config/profmanage.php:277 #: ../templates/config/profmanage.php:281 ../help/help.inc:146 @@ -14204,7 +14204,7 @@ msgstr "Zeige LDIF-Datei" #: ../lib/types/user.inc:824 msgid "Show account status" -msgstr "Accountstatus anzeigen" +msgstr "Kontostatus anzeigen" #: ../templates/3rdParty/pla/lib/QueryRender.php:144 #: ../templates/3rdParty/pla/htdocs/export_form.php:84 From 7514ec6b84f11cfc726796d4ea9a461ee8e2c98a Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Wed, 1 Jan 2020 17:04:14 +0100 Subject: [PATCH 31/51] webauthn --- lam/tests/lib/webauthnTest.php | 96 +++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 8 deletions(-) diff --git a/lam/tests/lib/webauthnTest.php b/lam/tests/lib/webauthnTest.php index 687f5f59..72fb32fb 100644 --- a/lam/tests/lib/webauthnTest.php +++ b/lam/tests/lib/webauthnTest.php @@ -1,15 +1,16 @@ database = $this + ->getMockBuilder(PublicKeyCredentialSourceRepositorySQLite::class) + ->setMethods(array('findOneByCredentialId', 'findAllForUserEntity', 'saveCredentialSource')) + ->getMock(); + $this->database->method('findOneByCredentialId')->willReturn(null); + $this->database->method('findAllForUserEntity')->willReturn(array()); + $this->database->method('saveCredentialSource')->willReturn(true); + + $this->manager = $this + ->getMockBuilder(WebauthnManager::class) + ->setMethods(array('getDatabase')) + ->getMock(); + $this->manager->method('getDatabase')->willReturn($this->database); + + $cfgMain = new \LAMCfgMain(); + $cfgMain->passwordMinLength = 3; + $logFile = tmpfile(); + $logFilePath = stream_get_meta_data($logFile)['uri']; + $cfgMain->logDestination = $logFilePath; + $_SESSION['cfgMain'] = $cfgMain; + + $file = tmpfile(); + $filePath = stream_get_meta_data($file)['uri']; + $config = new \LAMConfig($filePath); + $config->setTwoFactorAuthenticationDomain('domain'); + $_SESSION['config'] = $config; + } + + public function test_getAuthenticationObject() { + $authenticationObj = $this->manager->getAuthenticationObject('userDN', false); + $this->assertEquals(40, sizeof($authenticationObj->getChallenge())); + $this->assertEquals('domain', $authenticationObj->getRpId()); + } + + public function test_getRegistrationObject() { + $registrationObject = $this->manager->getRegistrationObject('userDn', false); + $this->assertEquals(40, sizeof($registrationObject->getChallenge())); + $this->assertEquals('domain', $registrationObject->getRp()->getId()); + } + + public function test_isRegistered() { + $this->database->method('findAllForUserEntity')->willReturn(array()); + $isRegistered = $this->manager->isRegistered('userDN'); + $this->assertFalse($isRegistered); + $this->database->method('findAllForUserEntity')->willReturn(array( + new PublicKeyCredentialSource( + "id1", + PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, + array(), + "atype", + new CertificateTrustPath(array('x5c' => 'test')), + \Ramsey\Uuid\Uuid::uuid1(), + "p1", + "uh1", + 1) + )); + $isRegistered = $this->manager->isRegistered('userDN'); + $this->assertTrue($isRegistered); + } + +} /** * Checks the webauthn database functionality. @@ -38,7 +118,7 @@ require_once 'lam/lib/webauthn.inc'; class PublicKeyCredentialSourceRepositorySQLiteTest extends TestCase { /** - * @var PublicKeyCredentialSourceRepositorySQLite + * @var \PHPUnit_Framework_MockObject_MockObject|PublicKeyCredentialSourceRepositorySQLite */ private $database; From 91e98b6926fca312fa51e31301f8bed40c6df073 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Wed, 1 Jan 2020 17:13:19 +0100 Subject: [PATCH 32/51] webauthn --- lam/tests/lib/webauthnDbTest.php | 124 +++++++++++++++++++++++++++++++ lam/tests/lib/webauthnTest.php | 93 ----------------------- 2 files changed, 124 insertions(+), 93 deletions(-) create mode 100644 lam/tests/lib/webauthnDbTest.php diff --git a/lam/tests/lib/webauthnDbTest.php b/lam/tests/lib/webauthnDbTest.php new file mode 100644 index 00000000..a4ffdbef --- /dev/null +++ b/lam/tests/lib/webauthnDbTest.php @@ -0,0 +1,124 @@ +database = $this + ->getMockBuilder(PublicKeyCredentialSourceRepositorySQLite::class) + ->setMethods(array('getPdoUrl')) + ->getMock(); + $file = tmpfile(); + $filePath = stream_get_meta_data($file)['uri']; + $this->database->method('getPdoUrl')->willReturn('sqlite:' . $filePath); + } + + /** + * Empty DB test + */ + public function test_findOneByCredentialId_emptyDb() { + $result = $this->database->findOneByCredentialId("test"); + $this->assertNull($result); + } + + /** + * Empty DB test + */ + public function test_findAllForUserEntity_emptyDb() { + $entity = new PublicKeyCredentialUserEntity("cn=test,dc=example", "cn=test,dc=example", "test", null); + + $result = $this->database->findAllForUserEntity($entity); + $this->assertEmpty($result); + } + + /** + * Save multiple credentials and read them. + */ + public function test_saveCredentialSource() { + $source1 = new PublicKeyCredentialSource( + "id1", + PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, + array(), + "atype", + new CertificateTrustPath(array('x5c' => 'test')), + \Ramsey\Uuid\Uuid::uuid1(), + "p1", + "uh1", + 1); + $this->database->saveCredentialSource($source1); + $source2 = new PublicKeyCredentialSource( + "id2", + PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, + array(), + "atype", + new CertificateTrustPath(array('x5c' => 'test')), + \Ramsey\Uuid\Uuid::uuid1(), + "p2", + "uh1", + 1); + $this->database->saveCredentialSource($source2); + $source3 = new PublicKeyCredentialSource( + "id3", + PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, + array(), + "atype", + new CertificateTrustPath(array('x5c' => 'test')), + \Ramsey\Uuid\Uuid::uuid1(), + "p3", + "uh2", + 1); + $this->database->saveCredentialSource($source3); + + $this->assertNotNull($this->database->findOneByCredentialId("id1")); + $this->assertNotNull($this->database->findOneByCredentialId("id2")); + $this->assertNotNull($this->database->findOneByCredentialId("id3")); + $this->assertEquals(2, sizeof( + $this->database->findAllForUserEntity(new PublicKeyCredentialUserEntity("uh1", "uh1", "uh1", null)) + )); + $this->assertEquals(1, sizeof( + $this->database->findAllForUserEntity(new PublicKeyCredentialUserEntity("uh2", "uh2", "uh2", null)) + )); + } + +} + diff --git a/lam/tests/lib/webauthnTest.php b/lam/tests/lib/webauthnTest.php index 72fb32fb..f3216161 100644 --- a/lam/tests/lib/webauthnTest.php +++ b/lam/tests/lib/webauthnTest.php @@ -3,7 +3,6 @@ namespace LAM\LOGIN\WEBAUTHN; use \PHPUnit\Framework\TestCase; use \Webauthn\PublicKeyCredentialDescriptor; -use \Webauthn\PublicKeyCredentialUserEntity; use \Webauthn\PublicKeyCredentialSource; use \Webauthn\TrustPath\CertificateTrustPath; @@ -109,95 +108,3 @@ class WebauthnManagerTest extends TestCase { } } - -/** - * Checks the webauthn database functionality. - * - * @author Roland Gruber - */ -class PublicKeyCredentialSourceRepositorySQLiteTest extends TestCase { - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|PublicKeyCredentialSourceRepositorySQLite - */ - private $database; - - protected function setUp(): void { - $this->database = $this - ->getMockBuilder(PublicKeyCredentialSourceRepositorySQLite::class) - ->setMethods(array('getPdoUrl')) - ->getMock(); - $file = tmpfile(); - $filePath = stream_get_meta_data($file)['uri']; - $this->database->method('getPdoUrl')->willReturn('sqlite:' . $filePath); - } - - /** - * Empty DB test - */ - public function test_findOneByCredentialId_emptyDb() { - $result = $this->database->findOneByCredentialId("test"); - $this->assertNull($result); - } - - /** - * Empty DB test - */ - public function test_findAllForUserEntity_emptyDb() { - $entity = new PublicKeyCredentialUserEntity("cn=test,dc=example", "cn=test,dc=example", "test", null); - - $result = $this->database->findAllForUserEntity($entity); - $this->assertEmpty($result); - } - - /** - * Save multiple credentials and read them. - */ - public function test_saveCredentialSource() { - $source1 = new PublicKeyCredentialSource( - "id1", - PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, - array(), - "atype", - new CertificateTrustPath(array('x5c' => 'test')), - \Ramsey\Uuid\Uuid::uuid1(), - "p1", - "uh1", - 1); - $this->database->saveCredentialSource($source1); - $source2 = new PublicKeyCredentialSource( - "id2", - PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, - array(), - "atype", - new CertificateTrustPath(array('x5c' => 'test')), - \Ramsey\Uuid\Uuid::uuid1(), - "p2", - "uh1", - 1); - $this->database->saveCredentialSource($source2); - $source3 = new PublicKeyCredentialSource( - "id3", - PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, - array(), - "atype", - new CertificateTrustPath(array('x5c' => 'test')), - \Ramsey\Uuid\Uuid::uuid1(), - "p3", - "uh2", - 1); - $this->database->saveCredentialSource($source3); - - $this->assertNotNull($this->database->findOneByCredentialId("id1")); - $this->assertNotNull($this->database->findOneByCredentialId("id2")); - $this->assertNotNull($this->database->findOneByCredentialId("id3")); - $this->assertEquals(2, sizeof( - $this->database->findAllForUserEntity(new PublicKeyCredentialUserEntity("uh1", "uh1", "uh1", null)) - )); - $this->assertEquals(1, sizeof( - $this->database->findAllForUserEntity(new PublicKeyCredentialUserEntity("uh2", "uh2", "uh2", null)) - )); - } - -} - From 9d9c37a44aad9572731a168904c8824593551915 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Wed, 1 Jan 2020 17:17:35 +0100 Subject: [PATCH 33/51] webauthn --- lam/lib/2factor.inc | 2 -- lam/lib/webauthn.inc | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index e3d26062..b20ba8ad 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -594,8 +594,6 @@ class WebauthnProvider extends BaseProvider { $response = base64_decode($_POST['sig_response']); return $webauthnManager->isValidAuthentication($response, $userDn); } - logNewMessage(LOG_ERR, 'Webauthn authentication failed'); - return false; } } diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index 2fa54a44..d7402daf 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -454,7 +454,7 @@ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSo $statement->execute(array(':userid' => $publicKeyCredentialUserEntity->getId())); $results = $statement->fetchAll(); foreach ($results as $result) { - $jsonArray = json_decode($results[0]['credentialSource'], true); + $jsonArray = json_decode($result['credentialSource'], true); $credentials[] = PublicKeyCredentialSource::createFromArray($jsonArray); } } @@ -549,7 +549,7 @@ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSo protected function tableExists(&$pdo, $tableName) { try { $result = $pdo->query("SELECT 1 FROM $tableName LIMIT 1"); - return ($result === false) ? false : true; + return ($result !== false); } catch (\PDOException $e) { return false; } From f97359f466d929d16dd723d3a7f4eb24d5ba469a Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Wed, 1 Jan 2020 18:08:30 +0100 Subject: [PATCH 34/51] webauthn --- lam/lib/2factor.inc | 15 ++++- lam/tests/lib/2factorWebauthnTest.php | 86 +++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 lam/tests/lib/2factorWebauthnTest.php diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index b20ba8ad..713836e3 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -491,7 +491,7 @@ class WebauthnProvider extends BaseProvider { * * @param TwoFactorConfiguration $config configuration */ - public function __construct(&$config) { + public function __construct($config) { $this->config = $config; } @@ -550,7 +550,7 @@ class WebauthnProvider extends BaseProvider { $row->add(new htmlDiv(null, $errorMessage, array('hidden webauthn-error')), 12); if ($this->config->twoFactorAuthenticationOptional === true) { include_once __DIR__ . '/webauthn.inc'; - $webauthnManager = new WebauthnManager(); + $webauthnManager = $this->getWebauthnManager(); $hasTokens = $webauthnManager->isRegistered($userDn); if (!$hasTokens) { $skipButton = new htmlButton('skip_webauthn', _('Skip')); @@ -565,6 +565,15 @@ class WebauthnProvider extends BaseProvider { $row->add(new htmlJavaScript('window.lam.webauthn.start(\'' . $pathPrefix . '\');'), 0); } + /** + * Returns the webauthn manager. + * + * @return WebauthnManager manager + */ + public function getWebauthnManager() { + return new WebauthnManager(); + } + /** * {@inheritDoc} * @see \LAM\LIB\TWO_FACTOR\TwoFactorProvider::verify2ndFactor() @@ -572,7 +581,7 @@ class WebauthnProvider extends BaseProvider { public function verify2ndFactor($user, $password, $serial, $twoFactorInput) { logNewMessage(LOG_DEBUG, 'WebauthnProvider: Checking 2nd factor for ' . $user); include_once __DIR__ . '/webauthn.inc'; - $webauthnManager = new WebauthnManager(); + $webauthnManager = $this->getWebauthnManager(); if (!empty($_SESSION['ldap'])) { $userDn = $_SESSION['ldap']->getUserName(); } diff --git a/lam/tests/lib/2factorWebauthnTest.php b/lam/tests/lib/2factorWebauthnTest.php new file mode 100644 index 00000000..7f0cd39b --- /dev/null +++ b/lam/tests/lib/2factorWebauthnTest.php @@ -0,0 +1,86 @@ +config = new TwoFactorConfiguration(); + } + + public function test_getSerials() { + $provider = new WebauthnProvider($this->config); + + $this->assertNotEmpty($provider->getSerials('user', 'password')); + } + + public function test_isShowSubmitButton() { + $provider = new WebauthnProvider($this->config); + + $this->assertFalse($provider->isShowSubmitButton()); + } + + public function test_hasCustomInputForm() { + $provider = new WebauthnProvider($this->config); + + $this->assertTrue($provider->hasCustomInputForm()); + } + + public function test_addCustomInput() { + $this->config->twoFactorAuthenticationOptional = true; + $manager = $this + ->getMockBuilder(WebauthnManager::class) + ->setMethods(array('isRegistered')) + ->getMock(); + $manager->method('isRegistered')->willReturn(false); + $provider = $this + ->getMockBuilder(WebauthnProvider::class) + ->setConstructorArgs(array($this->config)) + ->setMethods(array('getWebauthnManager')) + ->getMock(); + $provider->method('getWebauthnManager')->willReturn($manager); + $row = new \htmlResponsiveRow(); + + $provider->addCustomInput($row, 'userDn'); + $tabindex = 0; + ob_start(); + $row->generateHTML(null, array(), array(), false, $tabindex, 'none'); + $html = ob_get_contents(); + ob_end_clean(); + $this->assertContains('skip_webauthn', $html); + } + +} \ No newline at end of file From ef9b3dd64ec928f92107ab51248c6f9140428f7b Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Wed, 1 Jan 2020 18:12:29 +0100 Subject: [PATCH 35/51] webauthn --- lam/tests/lib/2factorWebauthnTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lam/tests/lib/2factorWebauthnTest.php b/lam/tests/lib/2factorWebauthnTest.php index 7f0cd39b..c8b77457 100644 --- a/lam/tests/lib/2factorWebauthnTest.php +++ b/lam/tests/lib/2factorWebauthnTest.php @@ -37,7 +37,7 @@ class WebauthnProviderTest extends TestCase { */ private $config; - protected function setUp() { + protected function setUp(): void { $this->config = new TwoFactorConfiguration(); } From 38addc429cac7568e85954a81ebe4b7518b78d6a Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 4 Jan 2020 18:28:25 +0100 Subject: [PATCH 36/51] webauthn --- .../manual-sources/chapter-configuration.xml | 26 + lam/help/help.inc | 2 + lam/lib/html.inc | 4 +- lam/lib/webauthn.inc | 56 ++ lam/style/500_layout.css | 3 + lam/templates/config/mainmanage.php | 554 +++++++++--------- lam/templates/lib/500_lam.js | 91 +++ lam/templates/misc/ajax.php | 123 +++- lam/tests/lib/webauthnDbTest.php | 48 ++ 9 files changed, 630 insertions(+), 277 deletions(-) diff --git a/lam/docs/manual-sources/chapter-configuration.xml b/lam/docs/manual-sources/chapter-configuration.xml index 8b69b1e4..d4d77ec1 100644 --- a/lam/docs/manual-sources/chapter-configuration.xml +++ b/lam/docs/manual-sources/chapter-configuration.xml @@ -655,6 +655,11 @@ Duo + + + Webauthn/FIDO2 + Configuration options: @@ -752,6 +757,27 @@ + Webauthn/FIDO2 + + Users will be asked to register a device during login if no + device is setup. + + + + Domain: Please enter the WebAuthn domain. This is the public + domain of the web server (e.g. "example.com"). Do not include + protocol or port. Browsers will reject authentication if the + domain does not match the web server domain. + + + + Optional: By default LAM will enforce to use a 2FA device + and reject users that do not setup one. You can set this check to + optional. But if a user has setup a device then this will always + be required. + + + diff --git a/lam/help/help.inc b/lam/help/help.inc index f3cc3c99..7f9692aa 100644 --- a/lam/help/help.inc +++ b/lam/help/help.inc @@ -179,6 +179,8 @@ $helpArray = array ( "Text" => _("Here you can input simple filter expressions (e.g. 'value' or 'v*'). The filter is case-insensitive.")), "251" => array ("Headline" => _("Remote server"), "Text" => _("Please enter the syslog remote server in format \"server:port\".")), + "252" => array ("Headline" => _("User DN"), + "Text" => _("Please enter a part of the user's DN to search for registered devices.")), "260" => array ("Headline" => _("Additional LDAP filter"), "Text" => _('Use this to enter an additional LDAP filter (e.g. "(cn!=admin)") to reduce the number of visible elements for this account type.') . ' ' . _('You can use the wildcard @@LOGIN_DN@@ which will be substituted with the DN of the user who is currently logged in to LAM.') diff --git a/lam/lib/html.inc b/lam/lib/html.inc index 14410395..52daa4db 100644 --- a/lam/lib/html.inc +++ b/lam/lib/html.inc @@ -1098,10 +1098,10 @@ class htmlButton extends htmlElement { } $id = ' id="btn_' . preg_replace('/[^a-zA-Z0-9_-]/', '', $this->name) . '"'; if ($this->isImageButton) { - echo ''; + echo 'getDataAttributesAsString() . '>'; } else { - echo '' . $this->value . ''; + echo 'getDataAttributesAsString() . '>' . $this->value . ''; // text buttons get JQuery style $icon = ''; if ($this->iconClass != null) { diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index d7402daf..9b1fabac 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -523,6 +523,62 @@ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSo return 'sqlite:' . $fileName; } + /** + * Returns if there are any credentials in the database. + * + * @return bool at least one credential in the database + */ + public function hasRegisteredCredentials() { + $pdo = $this->getPDO(); + $statement = $pdo->prepare('select count(*) from ' . self::TABLE_NAME); + $statement->execute(); + $results = $statement->fetchAll(); + return ($results[0][0] > 0); + } + + /** + * Performs a full-text search on the user names and returns all devices found. + * + * @param string $searchTerm search term for user field + * @return array list of devices array('dn' => ..., 'credentialId' => ..., 'lastUseTime' => ..., 'registrationTime' => ...) + */ + public function searchDevices(string $searchTerm) { + $pdo = $this->getPDO(); + $statement = $pdo->prepare('select * from ' . self::TABLE_NAME . ' where userId like :searchTerm'); + $statement->execute(array( + ':searchTerm' => '%' . $searchTerm . '%' + )); + $results = $statement->fetchAll(); + $devices = array(); + foreach ($results as $result) { + $devices[] = array( + 'dn' => $result['userId'], + 'credentialId' => $result['credentialId'], + 'lastUseTime' => $result['lastUseTime'], + 'registrationTime' => $result['registrationTime'] + ); + } + return $devices; + } + + /** + * Deletes a single device from the database. + * + * @param string $dn user DN + * @param string $credentialId credential id + * @return bool deletion was ok + */ + public function deleteDevice(string $dn, string $credentialId) { + logNewMessage(LOG_NOTICE, 'Delete webauthn device ' . $credentialId . ' of ' . $dn); + $pdo = $this->getPDO(); + $statement = $pdo->prepare('delete from ' . self::TABLE_NAME . ' where userId = :userId and credentialId = :credentialId'); + $result = $statement->execute(array( + ':userId' => $dn, + ':credentialId' => $credentialId + )); + return $statement->rowCount() > 0; + } + /** * Returns the PDO. * diff --git a/lam/style/500_layout.css b/lam/style/500_layout.css index 32e07b6a..86a508ea 100644 --- a/lam/style/500_layout.css +++ b/lam/style/500_layout.css @@ -547,6 +547,9 @@ input.markOk { text-decoration: line-through; } +div.lam-webauthn-results { + max-height: 10rem; +} /** * table style for delete.php diff --git a/lam/templates/config/mainmanage.php b/lam/templates/config/mainmanage.php index 9ebab21c..1ef2836c 100644 --- a/lam/templates/config/mainmanage.php +++ b/lam/templates/config/mainmanage.php @@ -1,5 +1,6 @@ setPassword($_POST['masterpassword']); $msg = _("New master password set successfully."); unset($_SESSION["mainconf_password"]); - } - else { + } else { $errors[] = _("Master passwords are different or empty!"); } } @@ -126,8 +127,7 @@ if (isset($_POST['submitFormData'])) { } } $allowedHosts = implode(",", $allowedHostsList); - } - else { + } else { $allowedHosts = ""; } $cfg->allowedHosts = $allowedHosts; @@ -150,8 +150,7 @@ if (isset($_POST['submitFormData'])) { } } $allowedHostsSelfService = implode(",", $allowedHostsSelfServiceList); - } - else { + } else { $allowedHostsSelfService = ""; } $cfg->allowedHostsSelfService = $allowedHostsSelfService; @@ -169,22 +168,18 @@ if (isset($_POST['submitFormData'])) { // set log destination if ($_POST['logDestination'] == "none") { $cfg->logDestination = "NONE"; - } - elseif ($_POST['logDestination'] == "syslog") { + } elseif ($_POST['logDestination'] == "syslog") { $cfg->logDestination = "SYSLOG"; - } - elseif ($_POST['logDestination'] == "remote") { + } elseif ($_POST['logDestination'] == "remote") { $cfg->logDestination = "REMOTE:" . $_POST['logRemote']; $remoteParts = explode(':', $_POST['logRemote']); if ((sizeof($remoteParts) !== 2) || !get_preg($remoteParts[0], 'DNSname') || !get_preg($remoteParts[1], 'digit')) { $errors[] = _("Please enter a valid remote server in format \"server:port\"."); } - } - else { + } else { if (isset($_POST['logFile']) && ($_POST['logFile'] != "") && preg_match("/^[a-z0-9\\/\\\\:\\._-]+$/i", $_POST['logFile'])) { $cfg->logDestination = $_POST['logFile']; - } - else { + } else { $errors[] = _("The log file is empty or contains invalid characters! Valid characters are: a-z, A-Z, 0-9, /, \\, ., :, _ and -."); } } @@ -207,16 +202,14 @@ if (isset($_POST['submitFormData'])) { if (isset($_POST['sslCaCertUpload'])) { if (!isset($_FILES['sslCaCert']) || ($_FILES['sslCaCert']['size'] == 0)) { $errors[] = _('No file selected.'); - } - else { + } else { $handle = fopen($_FILES['sslCaCert']['tmp_name'], "r"); $data = fread($handle, 10000000); fclose($handle); $sslReturn = $cfg->uploadSSLCaCert($data); if ($sslReturn !== true) { $errors[] = $sslReturn; - } - else { + } else { $messages[] = _('You might need to restart your webserver for changes to take effect.'); } } @@ -237,12 +230,10 @@ if (isset($_POST['submitFormData'])) { $messages[] = _('Imported certificate from server.'); $messages[] = _('You might need to restart your webserver for changes to take effect.'); $cfg->uploadSSLCaCert($pemResult); - } - else { + } else { $errors[] = _('Unable to import server certificate. Please use the upload function.'); } - } - else { + } else { $errors[] = _('Invalid server name. Please enter "server" or "server:port".'); } } @@ -270,264 +261,279 @@ if (isset($_POST['submitFormData'])) { echo $_SESSION['header']; printHeaderContents(_("Edit general settings"), '../..'); ?> - - - - - - -
- -
-
- - + + + + + + +
+ +
+
+ + -add(new htmlTitle(_('General settings')), 12); + $row = new htmlResponsiveRow(); + $row->add(new htmlTitle(_('General settings')), 12); -// print messages -for ($i = 0; $i < sizeof($errors); $i++) { - $row->add(new htmlStatusMessage("ERROR", $errors[$i]), 12); -} -for ($i = 0; $i < sizeof($messages); $i++) { - $row->add(new htmlStatusMessage("INFO", $messages[$i]), 12); -} - -// check if config file is writable -if (!$cfg->isWritable()) { - $row->add(new htmlStatusMessage('WARN', 'The config file is not writable.', 'Your changes cannot be saved until you make the file writable for the webserver user.'), 12); -} - -// license -if (isLAMProVersion()) { - $row->add(new htmlSubTitle(_('Licence')), 12); - $row->add(new htmlResponsiveInputTextarea('license', implode("\n", $cfg->getLicenseLines()), null, 10, _('Licence'), '287'), 12); - - $row->add(new htmlSpacer(null, '1rem'), true); -} - -// security settings -$row->add(new htmlSubTitle(_("Security settings")), 12); -$options = array(5, 10, 20, 30, 60, 90, 120, 240); -$row->add(new htmlResponsiveSelect('sessionTimeout', $options, array($cfg->sessionTimeout), _("Session timeout"), '238'), 12); -$row->add(new htmlResponsiveInputTextarea('allowedHosts', implode("\n", explode(",", $cfg->allowedHosts)), null, '7', _("Allowed hosts"), '241'), 12); -if (isLAMProVersion()) { - $row->add(new htmlResponsiveInputTextarea('allowedHostsSelfService', implode("\n", explode(",", $cfg->allowedHostsSelfService)), null, '7', _("Allowed hosts (self service)"), '241'), 12); -} -$encryptSession = ($cfg->encryptSession === 'true'); -$encryptSessionBox = new htmlResponsiveInputCheckbox('encryptSession', $encryptSession, _('Encrypt session'), '245'); -$encryptSessionBox->setIsEnabled(function_exists('openssl_random_pseudo_bytes')); -$row->add($encryptSessionBox, 12); -// SSL certificate -$row->addVerticalSpacer('1rem'); -$row->addLabel(new htmlOutputText(_('SSL certificates'))); -$sslMethod = _('use system certificates'); -$sslFileName = $cfg->getSSLCaCertTempFileName(); -if ($sslFileName != null) { - $sslMethod = _('use custom CA certificates'); -} -$sslDelSaveGroup = new htmlGroup(); -$sslDelSaveGroup->addElement(new htmlOutputText($sslMethod)); -$sslDelSaveGroup->addElement(new htmlSpacer('5px', null)); -// delete+download button -if ($sslFileName != null) { - $sslDownloadBtn = new htmlLink('', '../../tmp/' . $sslFileName, '../../graphics/save.png'); - $sslDownloadBtn->setTargetWindow('_blank'); - $sslDownloadBtn->setTitle(_('Download CA certificates')); - $sslDelSaveGroup->addElement($sslDownloadBtn); - $sslDeleteBtn = new htmlButton('sslCaCertDelete', 'delete.png', true); - $sslDeleteBtn->setTitle(_('Delete all CA certificates')); - $sslDelSaveGroup->addElement($sslDeleteBtn); -} -$sslDelSaveGroup->addElement(new htmlHelpLink('204')); -$row->addField($sslDelSaveGroup); -$row->addLabel(new htmlInputFileUpload('sslCaCert')); -$sslUploadBtn = new htmlButton('sslCaCertUpload', _('Upload')); -$sslUploadBtn->setIconClass('upButton'); -$sslUploadBtn->setTitle(_('Upload CA certificate in DER/PEM format.')); -$row->addField($sslUploadBtn); -if (function_exists('stream_socket_client') && function_exists('stream_context_get_params')) { - $sslImportServerUrl = !empty($_POST['serverurl']) ? $_POST['serverurl'] : 'ldaps://'; - $serverUrlUpload = new htmlInputField('serverurl', $sslImportServerUrl); - $row->addLabel($serverUrlUpload); - $sslImportBtn = new htmlButton('sslCaCertImport', _('Import from server')); - $sslImportBtn->setIconClass('downButton'); - $sslImportBtn->setTitle(_('Imports the certificate directly from your LDAP server.')); - $row->addField($sslImportBtn); -} - -$sslCerts = $cfg->getSSLCaCertificates(); -if (sizeof($sslCerts) > 0) { - $certsTitles = array(_('Common name'), _('Valid to'), _('Serial number'), _('Delete')); - $certsData = array(); - for ($i = 0; $i < sizeof($sslCerts); $i++) { - $serial = isset($sslCerts[$i]['serialNumber']) ? $sslCerts[$i]['serialNumber'] : ''; - $validTo = isset($sslCerts[$i]['validTo_time_t']) ? $sslCerts[$i]['validTo_time_t'] : ''; - $cn = isset($sslCerts[$i]['subject']['CN']) ? $sslCerts[$i]['subject']['CN'] : ''; - $delBtn = new htmlButton('deleteCert_' . $i, 'delete.png', true); - $certsData[] = array( - new htmlOutputText($cn), - new htmlOutputText($validTo), - new htmlOutputText($serial), - $delBtn - ); + // print messages + for ($i = 0; $i < sizeof($errors); $i++) { + $row->add(new htmlStatusMessage("ERROR", $errors[$i]), 12); + } + for ($i = 0; $i < sizeof($messages); $i++) { + $row->add(new htmlStatusMessage("INFO", $messages[$i]), 12); } - $certsTable = new \htmlResponsiveTable($certsTitles, $certsData); - $row->add($certsTable, 12); -} -// password policy -$row->add(new htmlSubTitle(_("Password policy")), 12); -$options20 = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20); -$options4 = array(0, 1, 2, 3, 4); -$row->add(new htmlResponsiveSelect('passwordMinLength', $options20, array($cfg->passwordMinLength), _('Minimum password length'), '242'), 12); -$row->addVerticalSpacer('1rem'); -$row->add(new htmlResponsiveSelect('passwordMinLower', $options20, array($cfg->passwordMinLower), _('Minimum lowercase characters'), '242'), 12); -$row->add(new htmlResponsiveSelect('passwordMinUpper', $options20, array($cfg->passwordMinUpper), _('Minimum uppercase characters'), '242'), 12); -$row->add(new htmlResponsiveSelect('passwordMinNumeric', $options20, array($cfg->passwordMinNumeric), _('Minimum numeric characters'), '242'), 12); -$row->add(new htmlResponsiveSelect('passwordMinSymbol', $options20, array($cfg->passwordMinSymbol), _('Minimum symbolic characters'), '242'), 12); -$row->add(new htmlResponsiveSelect('passwordMinClasses', $options4, array($cfg->passwordMinClasses), _('Minimum character classes'), '242'), 12); -$row->addVerticalSpacer('1rem'); -$rulesCountOptions = array(_('all') => '-1', '3' => '3', '4' => '4'); -$rulesCountSelect = new htmlResponsiveSelect('passwordRulesCount', $rulesCountOptions, array($cfg->checkedRulesCount), _('Number of rules that must match'), '246'); -$rulesCountSelect->setHasDescriptiveElements(true); -$row->add($rulesCountSelect, 12); -$passwordMustNotContainUser = ($cfg->passwordMustNotContainUser === 'true'); -$row->add(new htmlResponsiveInputCheckbox('passwordMustNotContainUser',$passwordMustNotContainUser , _('Password must not contain user name'), '247'), 12); -$passwordMustNotContain3Chars = ($cfg->passwordMustNotContain3Chars === 'true'); -$row->add(new htmlResponsiveInputCheckbox('passwordMustNotContain3Chars', $passwordMustNotContain3Chars, _('Password must not contain part of user/first/last name'), '248'), 12); -if (function_exists('curl_init')) { + // check if config file is writable + if (!$cfg->isWritable()) { + $row->add(new htmlStatusMessage('WARN', 'The config file is not writable.', 'Your changes cannot be saved until you make the file writable for the webserver user.'), 12); + } + + // license + if (isLAMProVersion()) { + $row->add(new htmlSubTitle(_('Licence')), 12); + $row->add(new htmlResponsiveInputTextarea('license', implode("\n", $cfg->getLicenseLines()), null, 10, _('Licence'), '287'), 12); + + $row->add(new htmlSpacer(null, '1rem'), true); + } + + // security settings + $row->add(new htmlSubTitle(_("Security settings")), 12); + $options = array(5, 10, 20, 30, 60, 90, 120, 240); + $row->add(new htmlResponsiveSelect('sessionTimeout', $options, array($cfg->sessionTimeout), _("Session timeout"), '238'), 12); + $row->add(new htmlResponsiveInputTextarea('allowedHosts', implode("\n", explode(",", $cfg->allowedHosts)), null, '7', _("Allowed hosts"), '241'), 12); + if (isLAMProVersion()) { + $row->add(new htmlResponsiveInputTextarea('allowedHostsSelfService', implode("\n", explode(",", $cfg->allowedHostsSelfService)), null, '7', _("Allowed hosts (self service)"), '241'), 12); + } + $encryptSession = ($cfg->encryptSession === 'true'); + $encryptSessionBox = new htmlResponsiveInputCheckbox('encryptSession', $encryptSession, _('Encrypt session'), '245'); + $encryptSessionBox->setIsEnabled(function_exists('openssl_random_pseudo_bytes')); + $row->add($encryptSessionBox, 12); + // SSL certificate $row->addVerticalSpacer('1rem'); - $row->add(new htmlResponsiveInputField(_('External password check'), 'externalPwdCheckUrl', $cfg->externalPwdCheckUrl, '249'), 12); -} - -// logging -$row->add(new htmlSubTitle(_("Logging")), 12); -$levelOptions = array(_("Debug") => LOG_DEBUG, _("Notice") => LOG_NOTICE, _("Warning") => LOG_WARNING, _("Error") => LOG_ERR); -$levelSelect = new htmlResponsiveSelect('logLevel', $levelOptions, array($cfg->logLevel), _("Log level"), '239'); -$levelSelect->setHasDescriptiveElements(true); -$row->add($levelSelect, 12); -$destinationOptions = array( - _("No logging") => "none", - _("System logging") => "syslog", - _("File") => 'file', - _("Remote") => 'remote', -); -$destinationSelected = 'file'; -$destinationPath = $cfg->logDestination; -$destinationRemote = ''; -if ($cfg->logDestination == 'NONE') { - $destinationSelected = 'none'; - $destinationPath = ''; -} -elseif ($cfg->logDestination == 'SYSLOG') { - $destinationSelected = 'syslog'; - $destinationPath = ''; -} -elseif (strpos($cfg->logDestination, 'REMOTE') === 0) { - $destinationSelected = 'remote'; - $remoteParts = explode(':', $cfg->logDestination, 2); - $destinationRemote = empty($remoteParts[1]) ? '' : $remoteParts[1]; - $destinationPath = ''; -} -$logDestinationSelect = new htmlResponsiveSelect('logDestination', $destinationOptions, array($destinationSelected), _("Log destination"), '240'); -$logDestinationSelect->setTableRowsToHide(array( - 'none' => array('logFile', 'logRemote'), - 'syslog' => array('logFile', 'logRemote'), - 'remote' => array('logFile'), - 'file' => array('logRemote'), -)); -$logDestinationSelect->setTableRowsToShow(array( - 'file' => array('logFile'), - 'remote' => array('logRemote'), -)); -$logDestinationSelect->setHasDescriptiveElements(true); -$row->add($logDestinationSelect, 12); -$row->add(new htmlResponsiveInputField(_('File'), 'logFile', $destinationPath), 12); -$row->add(new htmlResponsiveInputField(_('Remote server'), 'logRemote', $destinationRemote, '251'), 12); -$errorLogOptions = array( - _('PHP system setting') => LAMCfgMain::ERROR_REPORTING_SYSTEM, - _('default') => LAMCfgMain::ERROR_REPORTING_DEFAULT, - _('all') => LAMCfgMain::ERROR_REPORTING_ALL -); -$errorLogSelect = new htmlResponsiveSelect('errorReporting', $errorLogOptions, array($cfg->errorReporting), _('PHP error reporting'), '244'); -$errorLogSelect->setHasDescriptiveElements(true); -$row->add($errorLogSelect, 12); - -// additional options -if (isLAMProVersion()) { - $row->add(new htmlSubTitle(_('Additional options')), 12); - $mailEOLOptions = array( - _('Default (\r\n)') => 'default', - _('Non-standard (\n)') => 'unix' - ); - $mailEOLSelect = new htmlResponsiveSelect('mailEOL', $mailEOLOptions, array($cfg->mailEOL), _('Email format'), '243'); - $mailEOLSelect->setHasDescriptiveElements(true); - $row->add($mailEOLSelect, 12); -} -$row->addVerticalSpacer('3rem'); - -// change master password -$row->add(new htmlSubTitle(_("Change master password")), 12); -$pwd1 = new htmlResponsiveInputField(_("New master password"), 'masterpassword', '', '235'); -$pwd1->setIsPassword(true, false, true); -$row->add($pwd1, 12); -$pwd2 = new htmlResponsiveInputField(_("Reenter password"), 'masterpassword2', ''); -$pwd2->setIsPassword(true, false, true); -$pwd2->setSameValueFieldID('masterpassword'); -$row->add($pwd2, 12); -$row->addVerticalSpacer('3rem'); - -// buttons -if ($cfg->isWritable()) { - $buttonTable = new htmlTable(); - $buttonTable->addElement(new htmlButton('submit', _("Ok"))); - $buttonTable->addElement(new htmlSpacer('1rem', null)); - $buttonTable->addElement(new htmlButton('cancel', _("Cancel"))); - $row->add($buttonTable, 12); - $row->add(new htmlHiddenInput('submitFormData', '1'), 12); -} - -$box = new htmlDiv(null, $row); -$box->setCSSClasses(array('ui-corner-all', 'roundedShadowBox')); -parseHtml(null, $box, array(), false, $tabindex, 'user'); - - -/** - * Formats an LDAP time string (e.g. from createTimestamp). - * - * @param String $time LDAP time value - * @return String formated time - */ -function formatSSLTimestamp($time) { - if (!empty($time)) { - $timeZone = 'UTC'; - $sysTimeZone = @date_default_timezone_get(); - if (!empty($sysTimeZone)) { - $timeZone = $sysTimeZone; - } - $date = new DateTime('@' . $time, new DateTimeZone($timeZone)); - return $date->format('d.m.Y'); + $row->addLabel(new htmlOutputText(_('SSL certificates'))); + $sslMethod = _('use system certificates'); + $sslFileName = $cfg->getSSLCaCertTempFileName(); + if ($sslFileName != null) { + $sslMethod = _('use custom CA certificates'); } - return ''; -} + $sslDelSaveGroup = new htmlGroup(); + $sslDelSaveGroup->addElement(new htmlOutputText($sslMethod)); + $sslDelSaveGroup->addElement(new htmlSpacer('5px', null)); + // delete+download button + if ($sslFileName != null) { + $sslDownloadBtn = new htmlLink('', '../../tmp/' . $sslFileName, '../../graphics/save.png'); + $sslDownloadBtn->setTargetWindow('_blank'); + $sslDownloadBtn->setTitle(_('Download CA certificates')); + $sslDelSaveGroup->addElement($sslDownloadBtn); + $sslDeleteBtn = new htmlButton('sslCaCertDelete', 'delete.png', true); + $sslDeleteBtn->setTitle(_('Delete all CA certificates')); + $sslDelSaveGroup->addElement($sslDeleteBtn); + } + $sslDelSaveGroup->addElement(new htmlHelpLink('204')); + $row->addField($sslDelSaveGroup); + $row->addLabel(new htmlInputFileUpload('sslCaCert')); + $sslUploadBtn = new htmlButton('sslCaCertUpload', _('Upload')); + $sslUploadBtn->setIconClass('upButton'); + $sslUploadBtn->setTitle(_('Upload CA certificate in DER/PEM format.')); + $row->addField($sslUploadBtn); + if (function_exists('stream_socket_client') && function_exists('stream_context_get_params')) { + $sslImportServerUrl = !empty($_POST['serverurl']) ? $_POST['serverurl'] : 'ldaps://'; + $serverUrlUpload = new htmlInputField('serverurl', $sslImportServerUrl); + $row->addLabel($serverUrlUpload); + $sslImportBtn = new htmlButton('sslCaCertImport', _('Import from server')); + $sslImportBtn->setIconClass('downButton'); + $sslImportBtn->setTitle(_('Imports the certificate directly from your LDAP server.')); + $row->addField($sslImportBtn); + } + + $sslCerts = $cfg->getSSLCaCertificates(); + if (sizeof($sslCerts) > 0) { + $certsTitles = array(_('Common name'), _('Valid to'), _('Serial number'), _('Delete')); + $certsData = array(); + for ($i = 0; $i < sizeof($sslCerts); $i++) { + $serial = isset($sslCerts[$i]['serialNumber']) ? $sslCerts[$i]['serialNumber'] : ''; + $validTo = isset($sslCerts[$i]['validTo_time_t']) ? $sslCerts[$i]['validTo_time_t'] : ''; + $cn = isset($sslCerts[$i]['subject']['CN']) ? $sslCerts[$i]['subject']['CN'] : ''; + $delBtn = new htmlButton('deleteCert_' . $i, 'delete.png', true); + $certsData[] = array( + new htmlOutputText($cn), + new htmlOutputText($validTo), + new htmlOutputText($serial), + $delBtn + ); + } + $certsTable = new \htmlResponsiveTable($certsTitles, $certsData); + $row->add($certsTable, 12); + } + + // password policy + $row->add(new htmlSubTitle(_("Password policy")), 12); + $options20 = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20); + $options4 = array(0, 1, 2, 3, 4); + $row->add(new htmlResponsiveSelect('passwordMinLength', $options20, array($cfg->passwordMinLength), _('Minimum password length'), '242'), 12); + $row->addVerticalSpacer('1rem'); + $row->add(new htmlResponsiveSelect('passwordMinLower', $options20, array($cfg->passwordMinLower), _('Minimum lowercase characters'), '242'), 12); + $row->add(new htmlResponsiveSelect('passwordMinUpper', $options20, array($cfg->passwordMinUpper), _('Minimum uppercase characters'), '242'), 12); + $row->add(new htmlResponsiveSelect('passwordMinNumeric', $options20, array($cfg->passwordMinNumeric), _('Minimum numeric characters'), '242'), 12); + $row->add(new htmlResponsiveSelect('passwordMinSymbol', $options20, array($cfg->passwordMinSymbol), _('Minimum symbolic characters'), '242'), 12); + $row->add(new htmlResponsiveSelect('passwordMinClasses', $options4, array($cfg->passwordMinClasses), _('Minimum character classes'), '242'), 12); + $row->addVerticalSpacer('1rem'); + $rulesCountOptions = array(_('all') => '-1', '3' => '3', '4' => '4'); + $rulesCountSelect = new htmlResponsiveSelect('passwordRulesCount', $rulesCountOptions, array($cfg->checkedRulesCount), _('Number of rules that must match'), '246'); + $rulesCountSelect->setHasDescriptiveElements(true); + $row->add($rulesCountSelect, 12); + $passwordMustNotContainUser = ($cfg->passwordMustNotContainUser === 'true'); + $row->add(new htmlResponsiveInputCheckbox('passwordMustNotContainUser', $passwordMustNotContainUser, _('Password must not contain user name'), '247'), 12); + $passwordMustNotContain3Chars = ($cfg->passwordMustNotContain3Chars === 'true'); + $row->add(new htmlResponsiveInputCheckbox('passwordMustNotContain3Chars', $passwordMustNotContain3Chars, _('Password must not contain part of user/first/last name'), '248'), 12); + if (function_exists('curl_init')) { + $row->addVerticalSpacer('1rem'); + $row->add(new htmlResponsiveInputField(_('External password check'), 'externalPwdCheckUrl', $cfg->externalPwdCheckUrl, '249'), 12); + } + + // logging + $row->add(new htmlSubTitle(_("Logging")), 12); + $levelOptions = array(_("Debug") => LOG_DEBUG, _("Notice") => LOG_NOTICE, _("Warning") => LOG_WARNING, _("Error") => LOG_ERR); + $levelSelect = new htmlResponsiveSelect('logLevel', $levelOptions, array($cfg->logLevel), _("Log level"), '239'); + $levelSelect->setHasDescriptiveElements(true); + $row->add($levelSelect, 12); + $destinationOptions = array( + _("No logging") => "none", + _("System logging") => "syslog", + _("File") => 'file', + _("Remote") => 'remote', + ); + $destinationSelected = 'file'; + $destinationPath = $cfg->logDestination; + $destinationRemote = ''; + if ($cfg->logDestination == 'NONE') { + $destinationSelected = 'none'; + $destinationPath = ''; + } elseif ($cfg->logDestination == 'SYSLOG') { + $destinationSelected = 'syslog'; + $destinationPath = ''; + } elseif (strpos($cfg->logDestination, 'REMOTE') === 0) { + $destinationSelected = 'remote'; + $remoteParts = explode(':', $cfg->logDestination, 2); + $destinationRemote = empty($remoteParts[1]) ? '' : $remoteParts[1]; + $destinationPath = ''; + } + $logDestinationSelect = new htmlResponsiveSelect('logDestination', $destinationOptions, array($destinationSelected), _("Log destination"), '240'); + $logDestinationSelect->setTableRowsToHide(array( + 'none' => array('logFile', 'logRemote'), + 'syslog' => array('logFile', 'logRemote'), + 'remote' => array('logFile'), + 'file' => array('logRemote'), + )); + $logDestinationSelect->setTableRowsToShow(array( + 'file' => array('logFile'), + 'remote' => array('logRemote'), + )); + $logDestinationSelect->setHasDescriptiveElements(true); + $row->add($logDestinationSelect, 12); + $row->add(new htmlResponsiveInputField(_('File'), 'logFile', $destinationPath), 12); + $row->add(new htmlResponsiveInputField(_('Remote server'), 'logRemote', $destinationRemote, '251'), 12); + $errorLogOptions = array( + _('PHP system setting') => LAMCfgMain::ERROR_REPORTING_SYSTEM, + _('default') => LAMCfgMain::ERROR_REPORTING_DEFAULT, + _('all') => LAMCfgMain::ERROR_REPORTING_ALL + ); + $errorLogSelect = new htmlResponsiveSelect('errorReporting', $errorLogOptions, array($cfg->errorReporting), _('PHP error reporting'), '244'); + $errorLogSelect->setHasDescriptiveElements(true); + $row->add($errorLogSelect, 12); + + // additional options + if (isLAMProVersion()) { + $row->add(new htmlSubTitle(_('Additional options')), 12); + $mailEOLOptions = array( + _('Default (\r\n)') => 'default', + _('Non-standard (\n)') => 'unix' + ); + $mailEOLSelect = new htmlResponsiveSelect('mailEOL', $mailEOLOptions, array($cfg->mailEOL), _('Email format'), '243'); + $mailEOLSelect->setHasDescriptiveElements(true); + $row->add($mailEOLSelect, 12); + } + $row->addVerticalSpacer('3rem'); + + // webauthn management + if ((version_compare(phpversion(), '7.2.0') >= 0) + && extension_loaded('PDO') + && in_array('sqlite', \PDO::getAvailableDrivers())) { + include_once __DIR__ . '/../../lib/webauthn.inc'; + $database = new \LAM\LOGIN\WEBAUTHN\PublicKeyCredentialSourceRepositorySQLite(); + if ($database->hasRegisteredCredentials()) { + $row->add(new htmlSubTitle(_('Webauthn devices')), 12); + $row->add(new htmlResponsiveInputField(_('User DN'), 'webauthn_userDN', null, '252'), 12); + $row->addVerticalSpacer('0.5rem'); + $row->add(new htmlButton('webauthn_search', _('Search')), 12, 12, 12, 'text-center'); + $resultDiv = new htmlDiv('webauthn_results', new htmlOutputText(''), array('lam-webauthn-results')); + addSecurityTokenToSession(false); + $resultDiv->addDataAttribute('sec_token_value', getSecurityTokenValue()); + $row->add($resultDiv, 12); + } + } + + // change master password + $row->add(new htmlSubTitle(_("Change master password")), 12); + $pwd1 = new htmlResponsiveInputField(_("New master password"), 'masterpassword', '', '235'); + $pwd1->setIsPassword(true, false, true); + $row->add($pwd1, 12); + $pwd2 = new htmlResponsiveInputField(_("Reenter password"), 'masterpassword2', ''); + $pwd2->setIsPassword(true, false, true); + $pwd2->setSameValueFieldID('masterpassword'); + $row->add($pwd2, 12); + $row->addVerticalSpacer('3rem'); + + // buttons + if ($cfg->isWritable()) { + $buttonTable = new htmlTable(); + $buttonTable->addElement(new htmlButton('submit', _("Ok"))); + $buttonTable->addElement(new htmlSpacer('1rem', null)); + $buttonTable->addElement(new htmlButton('cancel', _("Cancel"))); + $row->add($buttonTable, 12); + $row->add(new htmlHiddenInput('submitFormData', '1'), 12); + } + + $box = new htmlDiv(null, $row); + $box->setCSSClasses(array('ui-corner-all', 'roundedShadowBox')); + parseHtml(null, $box, array(), false, $tabindex, 'user'); + /** + * Formats an LDAP time string (e.g. from createTimestamp). + * + * @param String $time LDAP time value + * @return String formated time + */ + function formatSSLTimestamp($time) { + if (!empty($time)) { + $timeZone = 'UTC'; + $sysTimeZone = @date_default_timezone_get(); + if (!empty($sysTimeZone)) { + $timeZone = $sysTimeZone; + } + $date = new DateTime('@' . $time, new DateTimeZone($timeZone)); + return $date->format('d.m.Y'); + } + return ''; + } -?> - -


+ ?> - + +


+ + diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index 871ddb84..26d57702 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -1522,6 +1522,96 @@ window.lam.webauthn.arrayToBase64String = function(input) { return btoa(String.fromCharCode(...input)); } +/** + * Sets up the device management on the main configuration page. + */ +window.lam.webauthn.setupDeviceManagement = function() { + const searchButton = jQuery('#btn_webauthn_search'); + if (searchButton) { + searchButton.click(window.lam.webauthn.searchDevices); + } +} + +/** + * Searches for devices via Ajax call. + * + * @param event button click event + * @returns {boolean} false + */ +window.lam.webauthn.searchDevices = function(event) { + if (event !== null) { + event.preventDefault(); + } + const resultDiv = jQuery('#webauthn_results'); + const tokenValue = resultDiv.data('sec_token_value'); + const searchData = jQuery('#webauthn_userDN').val(); + const data = { + action: 'search', + jsonInput: '', + sec_token: tokenValue, + searchTerm: searchData + }; + jQuery.ajax({ + url: '../misc/ajax.php?function=webauthnDevices', + method: 'POST', + data: data + }) + .done(function(jsonData) { + resultDiv.html(jsonData.content); + window.lam.webauthn.addDeviceActionListeners(); + }) + .fail(function() { + console.log('Webauthn search failed'); + }); + return false; +} + +/** + * Adds listeners to the device action buttons. + */ +window.lam.webauthn.addDeviceActionListeners = function() { + const inputs = jQuery('.webauthn-delete'); + inputs.each(function() { + jQuery(this).click(function(event) { + window.lam.webauthn.removeDevice(event); + }); + }); +} + +/** + * Removes a webauthn device. + * + * @param element button + */ +window.lam.webauthn.removeDevice = function(event) { + event.preventDefault(); + const element = jQuery(event.target); + const dn = element.data('dn'); + const credential = element.data('credential'); + const resultDiv = jQuery('#webauthn_results'); + const tokenValue = resultDiv.data('sec_token_value'); + const searchData = jQuery('#webauthn_userDN').val(); + const data = { + action: 'delete', + jsonInput: '', + sec_token: tokenValue, + dn: dn, + credentialId: credential + }; + jQuery.ajax({ + url: '../misc/ajax.php?function=webauthnDevices', + method: 'POST', + data: data + }) + .done(function(jsonData) { + resultDiv.html(jsonData.content); + }) + .fail(function() { + console.log('Webauthn device deletion failed'); + }); + return false; +} + jQuery(document).ready(function() { window.lam.gui.equalHeight(); window.lam.form.autoTrim(); @@ -1533,6 +1623,7 @@ jQuery(document).ready(function() { window.lam.html.activateLightboxes(); window.lam.html.preventEnter(); window.lam.dynamicSelect.activate(); + window.lam.webauthn.setupDeviceManagement(); }); /** diff --git a/lam/templates/misc/ajax.php b/lam/templates/misc/ajax.php index 61d1cb62..b759b844 100644 --- a/lam/templates/misc/ajax.php +++ b/lam/templates/misc/ajax.php @@ -1,5 +1,7 @@ manageWebauthn($isSelfService); die(); } + if ($function === 'webauthnDevices') { + $this->enforceUserIsLoggedInToMainConfiguration(); + $this->manageWebauthnDevices(); + die(); + } enforceUserIsLoggedIn(); if ($function == 'passwordChange') { $this->managePasswordChange($jsonInput); @@ -219,6 +227,102 @@ class Ajax { die(); } + /** + * Webauthn device management. + */ + private function manageWebauthnDevices() { + $action = $_POST['action']; + if ($action === 'search') { + $searchTerm = $_POST['searchTerm']; + if (!empty($searchTerm)) { + $this->manageWebauthnDevicesSearch($searchTerm); + } + } + elseif ($action === 'delete') { + $dn = $_POST['dn']; + $credentialId = $_POST['credentialId']; + if (!empty($dn) && !empty($credentialId)) { + $this->manageWebauthnDevicesDelete($dn, $credentialId); + } + } + } + + /** + * Searches for webauthn devices and prints the results as html. + * + * @param string $searchTerm search term + */ + private function manageWebauthnDevicesSearch($searchTerm) { + include_once __DIR__ . '/../../lib/webauthn.inc'; + $database = new \LAM\LOGIN\WEBAUTHN\PublicKeyCredentialSourceRepositorySQLite(); + $results = $database->searchDevices($searchTerm); + $row = new htmlResponsiveRow(); + $row->addVerticalSpacer('0.5rem'); + if (empty($results)) { + $row->add(new htmlStatusMessage('INFO', _('No devices found.')), 12); + } + else { + $titles = array( + _('User'), + _('Registration'), + _('Last use'), + _('Delete') + ); + $data = array(); + $id = 0; + foreach ($results as $result) { + $delButton = new htmlButton('deleteDevice' . $id, 'delete.png', true); + $delButton->addDataAttribute('credential', $result['credentialId']); + $delButton->addDataAttribute('dn', $result['dn']); + $delButton->setCSSClasses(array('webauthn-delete')); + $data[] = array( + new htmlOutputText($result['dn']), + new htmlOutputText(date('Y-m-d H:i:s', $result['registrationTime'])), + new htmlOutputText(date('Y-m-d H:i:s', $result['lastUseTime'])), + $delButton + ); + $id++; + } + $table = new htmlResponsiveTable($titles, $data); + $row->add($table, 12); + } + $row->addVerticalSpacer('2rem'); + $tabindex = 10000; + ob_start(); + $row->generateHTML('none', array(), array(), false, $tabindex, null); + $content = ob_get_contents(); + ob_end_clean(); + echo json_encode(array('content' => $content)); + } + + /** + * Deletes a webauthn device. + * + * @param string $dn user DN + * @param string $credentialId base64 encoded credential id + */ + private function manageWebauthnDevicesDelete($dn, $credentialId) { + include_once __DIR__ . '/../../lib/webauthn.inc'; + $database = new \LAM\LOGIN\WEBAUTHN\PublicKeyCredentialSourceRepositorySQLite(); + $success = $database->deleteDevice($dn, $credentialId); + if ($success) { + $message = new htmlStatusMessage('INFO', _('The device was deleted.')); + } + else { + $message = new htmlStatusMessage('ERROR', _('The device was not found.')); + } + $row = new htmlResponsiveRow(); + $row->addVerticalSpacer('0.5rem'); + $row->add($message, 12); + $row->addVerticalSpacer('2rem'); + ob_start(); + $tabindex = 50000; + $row->generateHTML('none', array(), array(), true, $tabindex, null); + $content = ob_get_contents(); + ob_end_clean(); + echo json_encode(array('content' => $content)); + } + /** * Handles DN selection fields. * @@ -341,6 +445,23 @@ class Ajax { return $dnList; } + /** + * Checks if the user entered the configuration master password. + * Dies if password is not set. + */ + private function enforceUserIsLoggedInToMainConfiguration() { + if (!isset($_SESSION['cfgMain'])) { + $cfg = new LAMCfgMain(); + } + else { + $cfg = $_SESSION['cfgMain']; + } + if (isset($_SESSION["mainconf_password"]) && ($cfg->checkPassword($_SESSION["mainconf_password"]))) { + return; + } + die(); + } + } diff --git a/lam/tests/lib/webauthnDbTest.php b/lam/tests/lib/webauthnDbTest.php index a4ffdbef..d6478b19 100644 --- a/lam/tests/lib/webauthnDbTest.php +++ b/lam/tests/lib/webauthnDbTest.php @@ -120,5 +120,53 @@ class PublicKeyCredentialSourceRepositorySQLiteTest extends TestCase { )); } + public function test_hasRegisteredCredentials() { + $this->assertFalse($this->database->hasRegisteredCredentials()); + $source1 = new PublicKeyCredentialSource( + "id1", + PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, + array(), + "atype", + new CertificateTrustPath(array('x5c' => 'test')), + \Ramsey\Uuid\Uuid::uuid1(), + "p1", + "uh1", + 1); + $this->database->saveCredentialSource($source1); + $this->assertTrue($this->database->hasRegisteredCredentials()); + } + + public function test_searchDevices() { + $source1 = new PublicKeyCredentialSource( + "id1", + PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, + array(), + "atype", + new CertificateTrustPath(array('x5c' => 'test')), + \Ramsey\Uuid\Uuid::uuid1(), + "p1", + "uh1", + 1); + $this->database->saveCredentialSource($source1); + $this->assertNotEmpty($this->database->searchDevices('h1')); + $this->assertEmpty($this->database->searchDevices('h2')); + } + + public function test_deleteDevice() { + $source1 = new PublicKeyCredentialSource( + "id1", + PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, + array(), + "atype", + new CertificateTrustPath(array('x5c' => 'test')), + \Ramsey\Uuid\Uuid::uuid1(), + "p1", + "uh1", + 1); + $this->database->saveCredentialSource($source1); + $this->assertTrue($this->database->deleteDevice('uh1', base64_encode('id1'))); + $this->assertFalse($this->database->deleteDevice('uh1', base64_encode('id2'))); + } + } From 7df361d6d0d6aa466cc47cd00d71439b836ff406 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 4 Jan 2020 19:06:01 +0100 Subject: [PATCH 37/51] webauthn --- lam/templates/config/mainmanage.php | 2 +- lam/templates/lib/500_lam.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lam/templates/config/mainmanage.php b/lam/templates/config/mainmanage.php index 1ef2836c..f4df10e5 100644 --- a/lam/templates/config/mainmanage.php +++ b/lam/templates/config/mainmanage.php @@ -473,7 +473,7 @@ printHeaderContents(_("Edit general settings"), '../..'); $database = new \LAM\LOGIN\WEBAUTHN\PublicKeyCredentialSourceRepositorySQLite(); if ($database->hasRegisteredCredentials()) { $row->add(new htmlSubTitle(_('Webauthn devices')), 12); - $row->add(new htmlResponsiveInputField(_('User DN'), 'webauthn_userDN', null, '252'), 12); + $row->add(new htmlResponsiveInputField(_('User DN'), 'webauthn_searchTerm', null, '252'), 12); $row->addVerticalSpacer('0.5rem'); $row->add(new htmlButton('webauthn_search', _('Search')), 12, 12, 12, 'text-center'); $resultDiv = new htmlDiv('webauthn_results', new htmlOutputText(''), array('lam-webauthn-results')); diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index 26d57702..08b8e793 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -1544,7 +1544,7 @@ window.lam.webauthn.searchDevices = function(event) { } const resultDiv = jQuery('#webauthn_results'); const tokenValue = resultDiv.data('sec_token_value'); - const searchData = jQuery('#webauthn_userDN').val(); + const searchData = jQuery('#webauthn_searchTerm').val(); const data = { action: 'search', jsonInput: '', @@ -1590,7 +1590,6 @@ window.lam.webauthn.removeDevice = function(event) { const credential = element.data('credential'); const resultDiv = jQuery('#webauthn_results'); const tokenValue = resultDiv.data('sec_token_value'); - const searchData = jQuery('#webauthn_userDN').val(); const data = { action: 'delete', jsonInput: '', From 3299d48e95f4c110c1eaab29ea995c22db00a8ff Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 4 Jan 2020 20:39:05 +0100 Subject: [PATCH 38/51] webauthn --- .../manual-sources/chapter-configuration.xml | 23 ++++++++++++++++++ .../manual-sources/images/configGeneral8.png | Bin 0 -> 36245 bytes 2 files changed, 23 insertions(+) create mode 100644 lam/docs/manual-sources/images/configGeneral8.png diff --git a/lam/docs/manual-sources/chapter-configuration.xml b/lam/docs/manual-sources/chapter-configuration.xml index d4d77ec1..00fbccb8 100644 --- a/lam/docs/manual-sources/chapter-configuration.xml +++ b/lam/docs/manual-sources/chapter-configuration.xml @@ -259,6 +259,29 @@
+
+ Webauthn devices + + Here you can delete any webauthn device registrations. This + section is only shown if at least one device is registered. + + Enter a part of the user's DN in the input box and perform a + search. LAM will show users and devices that match the search. You can + then delete a device registration. If the user has no more registered + devices then LAM will ask for registration on next login. + + Note: You cannot add any device here. This can only be done by the + user during login or self service. + + + + + + + + +
+
Change master password diff --git a/lam/docs/manual-sources/images/configGeneral8.png b/lam/docs/manual-sources/images/configGeneral8.png new file mode 100644 index 0000000000000000000000000000000000000000..97c0802d0cc8e07b336289ecd46b0024321a019f GIT binary patch literal 36245 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

Gtv5 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 Date: Sun, 5 Jan 2020 17:53:12 +0100 Subject: [PATCH 39/51] webauthn --- lam/lib/2factor.inc | 18 +++++++++---- lam/templates/config/mainmanage.php | 2 ++ lam/templates/lib/500_lam.js | 41 ++++++++++++++++++++++++----- lam/templates/misc/ajax.php | 3 +++ 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index 713836e3..a23497bf 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -1,6 +1,7 @@ add(new htmlStatusMessage('ERROR', 'Webauthn requires the sqlite PDO driver for PHP.'), 12); return; } + include_once __DIR__ . '/webauthn.inc'; + $webauthnManager = $this->getWebauthnManager(); + $hasTokens = $webauthnManager->isRegistered($userDn); + if ($hasTokens) { + $row->add(new htmlStatusMessage('INFO', _('Please authenticate with your security device.')), 12); + } + else { + $row->add(new htmlStatusMessage('INFO', _('Please register a security device.')), 12); + } + $row->addVerticalSpacer('2rem'); $pathPrefix = $this->config->isSelfService ? '../' : ''; $row->add(new htmlImage($pathPrefix . '../graphics/webauthn.svg'), 12); $row->addVerticalSpacer('1rem'); @@ -549,9 +560,6 @@ class WebauthnProvider extends BaseProvider { $errorMessage = new htmlStatusMessage('ERROR', '', _('This service requires a browser with "WebAuthn" support.')); $row->add(new htmlDiv(null, $errorMessage, array('hidden webauthn-error')), 12); if ($this->config->twoFactorAuthenticationOptional === true) { - include_once __DIR__ . '/webauthn.inc'; - $webauthnManager = $this->getWebauthnManager(); - $hasTokens = $webauthnManager->isRegistered($userDn); if (!$hasTokens) { $skipButton = new htmlButton('skip_webauthn', _('Skip')); $skipButton->setCSSClasses(array('fullwidth')); diff --git a/lam/templates/config/mainmanage.php b/lam/templates/config/mainmanage.php index f4df10e5..f2a6bfec 100644 --- a/lam/templates/config/mainmanage.php +++ b/lam/templates/config/mainmanage.php @@ -480,6 +480,8 @@ printHeaderContents(_("Edit general settings"), '../..'); addSecurityTokenToSession(false); $resultDiv->addDataAttribute('sec_token_value', getSecurityTokenValue()); $row->add($resultDiv, 12); + $confirmationDiv = new htmlDiv('webauthnDeleteConfirm', new htmlOutputText(_('Do you really want to remove this device?')), array('hidden')); + $row->add($confirmationDiv, 12); } } diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index 08b8e793..449b56eb 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -1586,6 +1586,34 @@ window.lam.webauthn.addDeviceActionListeners = function() { window.lam.webauthn.removeDevice = function(event) { event.preventDefault(); const element = jQuery(event.target); + const dialogTitle = element.data('dialogtitle'); + const okText = element.data('oktext'); + const cancelText = element.data('canceltext'); + let buttonList = {}; + buttonList[okText] = function() { + jQuery('#webauthnDeleteConfirm').dialog('close'); + window.lam.webauthn.sendRemoveDeviceRequest(element); + }; + buttonList[cancelText] = function() { + jQuery(this).dialog("close"); + }; + jQuery('#webauthnDeleteConfirm').dialog({ + modal: true, + title: dialogTitle, + dialogClass: 'defaultBackground', + buttons: buttonList, + width: 'auto' + }); + + return false; +} + +/** + * Sends the remove request to server. + * + * @param element button element + */ +window.lam.webauthn.sendRemoveDeviceRequest = function(element) { const dn = element.data('dn'); const credential = element.data('credential'); const resultDiv = jQuery('#webauthn_results'); @@ -1602,13 +1630,12 @@ window.lam.webauthn.removeDevice = function(event) { method: 'POST', data: data }) - .done(function(jsonData) { - resultDiv.html(jsonData.content); - }) - .fail(function() { - console.log('Webauthn device deletion failed'); - }); - return false; + .done(function(jsonData) { + resultDiv.html(jsonData.content); + }) + .fail(function() { + console.log('Webauthn device deletion failed'); + }); } jQuery(document).ready(function() { diff --git a/lam/templates/misc/ajax.php b/lam/templates/misc/ajax.php index b759b844..f709a544 100644 --- a/lam/templates/misc/ajax.php +++ b/lam/templates/misc/ajax.php @@ -274,6 +274,9 @@ class Ajax { $delButton = new htmlButton('deleteDevice' . $id, 'delete.png', true); $delButton->addDataAttribute('credential', $result['credentialId']); $delButton->addDataAttribute('dn', $result['dn']); + $delButton->addDataAttribute('dialogtitle', _('Remove device')); + $delButton->addDataAttribute('oktext', _('Ok')); + $delButton->addDataAttribute('canceltext', _('Cancel')); $delButton->setCSSClasses(array('webauthn-delete')); $data[] = array( new htmlOutputText($result['dn']), From 9e1e0634e6280017ee2ea6ea65aba3510c682f38 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 5 Jan 2020 19:05:55 +0100 Subject: [PATCH 40/51] webauthn --- lam/graphics/webauthn.png | Bin 0 -> 810 bytes lam/lib/tools/webauthn.inc | 133 +++++++++++++++++++++++++++++++ lam/lib/webauthn.inc | 4 +- lam/templates/lib/500_lam.js | 36 +++++++-- lam/templates/misc/ajax.php | 24 +++++- lam/templates/tools/webauthn.php | 117 +++++++++++++++++++++++++++ lam/tests/lib/webauthnDbTest.php | 5 +- 7 files changed, 306 insertions(+), 13 deletions(-) create mode 100644 lam/graphics/webauthn.png create mode 100644 lam/lib/tools/webauthn.inc create mode 100644 lam/templates/tools/webauthn.php diff --git a/lam/graphics/webauthn.png b/lam/graphics/webauthn.png new file mode 100644 index 0000000000000000000000000000000000000000..31072bb20ff9bf505cb2154d0186f69c6757ab2b GIT binary patch literal 810 zcmV+_1J(SAP)xgoiR(ln7$N*j<2nZ+6G?UQ&B zq~Z!cl6`G7SFXPH?6;2y-o5eRbfIN6mIxuhImmL4y?vaC@d}gU6;?I|^sJ}ySm()w zm!J6&!0ZTyffrp0XQt)|Bp}uyi@)DRc~4}7r9avno~qIt4k?_I9d1`(aeV_Sv*g7W(B zy^QSp(@aTSC!Bp^hKVSFfks81nMRGr8)Hn?5>(*ReFyxKck*~(_XzG+Ns}*Cc zEvd$ajdTcJ5U)`Pp(vb}{>6*eMn$5rLgj}9+}mEu+VcIox0HC>ZfC@y&`z_54=5PX zhDXduoL=}dbgetTwoFactorAuthentication() === TwoFactorProviderService::TWO_FACTOR_WEBAUTHN); + } + + /** + * Returns if a tool may be hidden by configuration in the LAM server profile. + * + * @return boolean hideable + */ + function isHideable() { + return true; + } + +} + +?> \ No newline at end of file diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index 9b1fabac..21e4fc6b 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -49,7 +49,7 @@ use \LAMException; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2019 Roland Gruber + Copyright (C) 2019 - 2020 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -546,7 +546,7 @@ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSo $pdo = $this->getPDO(); $statement = $pdo->prepare('select * from ' . self::TABLE_NAME . ' where userId like :searchTerm'); $statement->execute(array( - ':searchTerm' => '%' . $searchTerm . '%' + ':searchTerm' => $searchTerm )); $results = $statement->fetchAll(); $devices = array(); diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index 449b56eb..7a2856b1 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -1,7 +1,7 @@ /** This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2019 Roland Gruber + Copyright (C) 2003 - 2020 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1581,18 +1581,41 @@ window.lam.webauthn.addDeviceActionListeners = function() { /** * Removes a webauthn device. * - * @param element button + * @param event click event */ window.lam.webauthn.removeDevice = function(event) { event.preventDefault(); const element = jQuery(event.target); + window.lam.webauthn.removeDeviceDialog(element, 'webauthnDevices'); + return false; +} + +/** + * Removes a user's own webauthn device. + * + * @param event click event + */ +window.lam.webauthn.removeOwnDevice = function(event) { + event.preventDefault(); + const element = jQuery(event.target); + window.lam.webauthn.removeDeviceDialog(element, 'webauthnOwnDevices'); + return false; +} + +/** + * Opens the remove device diaog. + * + * @param element delete button + * @param action action for request (delete|deleteOwn) + */ +window.lam.webauthn.removeDeviceDialog = function(element, action) { const dialogTitle = element.data('dialogtitle'); const okText = element.data('oktext'); const cancelText = element.data('canceltext'); let buttonList = {}; buttonList[okText] = function() { jQuery('#webauthnDeleteConfirm').dialog('close'); - window.lam.webauthn.sendRemoveDeviceRequest(element); + window.lam.webauthn.sendRemoveDeviceRequest(element, action); }; buttonList[cancelText] = function() { jQuery(this).dialog("close"); @@ -1604,16 +1627,15 @@ window.lam.webauthn.removeDevice = function(event) { buttons: buttonList, width: 'auto' }); - - return false; } /** * Sends the remove request to server. * * @param element button element + * @param action action (delete|deleteOwn) */ -window.lam.webauthn.sendRemoveDeviceRequest = function(element) { +window.lam.webauthn.sendRemoveDeviceRequest = function(element, action) { const dn = element.data('dn'); const credential = element.data('credential'); const resultDiv = jQuery('#webauthn_results'); @@ -1626,7 +1648,7 @@ window.lam.webauthn.sendRemoveDeviceRequest = function(element) { credentialId: credential }; jQuery.ajax({ - url: '../misc/ajax.php?function=webauthnDevices', + url: '../misc/ajax.php?function=' + action, method: 'POST', data: data }) diff --git a/lam/templates/misc/ajax.php b/lam/templates/misc/ajax.php index f709a544..3cda46f1 100644 --- a/lam/templates/misc/ajax.php +++ b/lam/templates/misc/ajax.php @@ -15,7 +15,7 @@ use \LAMCfgMain; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2011 - 2019 Roland Gruber + Copyright (C) 2011 - 2020 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -159,6 +159,9 @@ class Ajax { ob_end_clean(); echo $jsonOut; } + elseif ($function === 'webauthnOwnDevices') { + $this->manageWebauthnOwnDevices(); + } } /** @@ -255,7 +258,7 @@ class Ajax { private function manageWebauthnDevicesSearch($searchTerm) { include_once __DIR__ . '/../../lib/webauthn.inc'; $database = new \LAM\LOGIN\WEBAUTHN\PublicKeyCredentialSourceRepositorySQLite(); - $results = $database->searchDevices($searchTerm); + $results = $database->searchDevices('%' . $searchTerm . '%'); $row = new htmlResponsiveRow(); $row->addVerticalSpacer('0.5rem'); if (empty($results)) { @@ -326,6 +329,23 @@ class Ajax { echo json_encode(array('content' => $content)); } + /** + * Manages requests to setup user's own webauthn devices. + */ + private function manageWebauthnOwnDevices() { + $action = $_POST['action']; + $dn = $_POST['dn']; + $sessionDn = $_SESSION['ldap']->getUserName(); + if ($sessionDn !== $dn) { + logNewMessage(LOG_ERR, 'Webauthn delete canceled, DN does not match.'); + die(); + } + if ($action === 'delete') { + $credentialId = $_POST['credentialId']; + $this->manageWebauthnDevicesDelete($dn, $credentialId); + } + } + /** * Handles DN selection fields. * diff --git a/lam/templates/tools/webauthn.php b/lam/templates/tools/webauthn.php new file mode 100644 index 00000000..3b81b592 --- /dev/null +++ b/lam/templates/tools/webauthn.php @@ -0,0 +1,117 @@ +'; +echo "

\n"; +$tabindex = 1; +$container = new htmlResponsiveRow(); + +$container->add(new htmlTitle(_("Webauthn devices")), 12); + +$userDn = $_SESSION['ldap']->getUserName(); +$database = new PublicKeyCredentialSourceRepositorySQLite(); +$results = $database->searchDevices($userDn); +$container->addVerticalSpacer('0.5rem'); +$buttonGroup = new htmlGroup(); +$reloadButton = new htmlButton('reload', _('Reload')); +$reloadButton->setIconClass('refreshButton'); +$buttonGroup->addElement($reloadButton); +$container->add($buttonGroup, 12); +$container->addVerticalSpacer('2rem'); +if (empty($results)) { + $container->add(new htmlStatusMessage('INFO', _('No devices found.')), 12); +} +else { + $titles = array( + _('Registration'), + _('Last use'), + _('Delete') + ); + $data = array(); + $id = 0; + foreach ($results as $result) { + $delButton = new htmlButton('deleteDevice' . $id, 'delete.png', true); + $delButton->addDataAttribute('credential', $result['credentialId']); + $delButton->addDataAttribute('dn', $result['dn']); + $delButton->addDataAttribute('dialogtitle', _('Remove device')); + $delButton->addDataAttribute('oktext', _('Ok')); + $delButton->addDataAttribute('canceltext', _('Cancel')); + $delButton->setOnClick('window.lam.webauthn.removeOwnDevice(event);'); + $data[] = array( + new htmlOutputText(date('Y-m-d H:i:s', $result['registrationTime'])), + new htmlOutputText(date('Y-m-d H:i:s', $result['lastUseTime'])), + $delButton + ); + $id++; + } + $table = new htmlResponsiveTable($titles, $data); + $tableDiv = new htmlDiv('webauthn_results', $table); + $tableDiv->addDataAttribute('sec_token_value', getSecurityTokenValue()); + $container->add($tableDiv, 12); +} +$container->addVerticalSpacer('2rem'); + +$confirmationDiv = new htmlDiv('webauthnDeleteConfirm', new htmlOutputText(_('Do you really want to remove this device?')), array('hidden')); +$container->add($confirmationDiv, 12); + + +parseHtml(null, $container, array(), false, $tabindex, 'user'); + +echo ''; +echo ''; +include __DIR__ . '/../../lib/adminFooter.inc'; + +?> diff --git a/lam/tests/lib/webauthnDbTest.php b/lam/tests/lib/webauthnDbTest.php index d6478b19..1d597564 100644 --- a/lam/tests/lib/webauthnDbTest.php +++ b/lam/tests/lib/webauthnDbTest.php @@ -148,8 +148,9 @@ class PublicKeyCredentialSourceRepositorySQLiteTest extends TestCase { "uh1", 1); $this->database->saveCredentialSource($source1); - $this->assertNotEmpty($this->database->searchDevices('h1')); - $this->assertEmpty($this->database->searchDevices('h2')); + $this->assertNotEmpty($this->database->searchDevices('uh1')); + $this->assertNotEmpty($this->database->searchDevices('%h1%')); + $this->assertEmpty($this->database->searchDevices('uh2')); } public function test_deleteDevice() { From 8a014f3a8ad29f9e76f9faff7900fbc9a1df7620 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Mon, 6 Jan 2020 12:26:50 +0100 Subject: [PATCH 41/51] webauthn --- lam/lib/webauthn.inc | 2 +- lam/style/500_layout.css | 4 +- lam/templates/lib/500_lam.js | 135 +++++++++++++++++++++++-------- lam/templates/misc/ajax.php | 2 +- lam/templates/tools/webauthn.php | 67 +++++++++++++-- 5 files changed, 168 insertions(+), 42 deletions(-) diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index 21e4fc6b..e396fec1 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -544,7 +544,7 @@ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSo */ public function searchDevices(string $searchTerm) { $pdo = $this->getPDO(); - $statement = $pdo->prepare('select * from ' . self::TABLE_NAME . ' where userId like :searchTerm'); + $statement = $pdo->prepare('select * from ' . self::TABLE_NAME . ' where userId like :searchTerm order by userId,registrationTime'); $statement->execute(array( ':searchTerm' => $searchTerm )); diff --git a/lam/style/500_layout.css b/lam/style/500_layout.css index 86a508ea..f881bd7e 100644 --- a/lam/style/500_layout.css +++ b/lam/style/500_layout.css @@ -548,7 +548,9 @@ input.markOk { } div.lam-webauthn-results { - max-height: 10rem; + max-height: 20rem; + overflow: auto; + padding: 1rem; } /** diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index 7a2856b1..2d716ede 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -841,6 +841,14 @@ window.lam.form.autoTrim = function() { window.lam.dialog = window.lam.dialog || {}; +/** + * Shows a dialog message. + * + * @param title dialog title + * @param okText ok button text + * @param divId DIV id with dialog content + * @param callbackFunction callback function (optional) + */ window.lam.dialog.showMessage = function(title, okText, divId, callbackFunction) { var buttonList = {}; buttonList[okText] = function() { @@ -1388,14 +1396,14 @@ window.lam.webauthn.run = function(prefix) { form.submit(); return; }); - var token = jQuery('#sec_token').val(); + const token = jQuery('#sec_token').val(); // check for webauthn support if (!navigator.credentials || (typeof(PublicKeyCredential) === "undefined")) { jQuery('.webauthn-error').show(); return; } - var data = { + const data = { action: 'status', jsonInput: '', sec_token: token @@ -1407,7 +1415,25 @@ window.lam.webauthn.run = function(prefix) { }) .done(function(jsonData) { if (jsonData.action === 'register') { - window.lam.webauthn.register(jsonData.registration); + const successCallback = function (publicKeyCredential) { + const form = jQuery("#2faform"); + const response = btoa(JSON.stringify(publicKeyCredential)); + form.append(''); + form.submit(); + }; + const errorCallback = function(error) { + let errorDiv = jQuery('#generic-webauthn-error'); + let buttonLabel = errorDiv.data('button'); + let dialogTitle = errorDiv.data('title'); + errorDiv.text(error.message); + window.lam.dialog.showMessage(dialogTitle, + buttonLabel, + 'generic-webauthn-error', + function () { + jQuery('#btn_logout').click(); + }); + }; + window.lam.webauthn.register(jsonData.registration, successCallback, errorCallback); } else if (jsonData.action === 'authenticate') { window.lam.webauthn.authenticate(jsonData.authentication); @@ -1422,23 +1448,27 @@ window.lam.webauthn.run = function(prefix) { * Performs a webauthn registration. * * @param publicKey registration object + * @param successCallback callback function in case of all went fine + * @param errorCallback callback function in case of an error */ -window.lam.webauthn.register = function(publicKey) { - publicKey.challenge = Uint8Array.from(window.atob(publicKey.challenge), c=>c.charCodeAt(0)); - publicKey.user.id = Uint8Array.from(window.atob(publicKey.user.id), c=>c.charCodeAt(0)); - publicKey.rp.icon = window.location.href.substring(0, window.location.href.lastIndexOf("/")) + publicKey.rp.icon; - if (publicKey.excludeCredentials) { - for (let i = 0; i < publicKey.excludeCredentials.length; i++) { - let idOrig = publicKey.excludeCredentials[i]['id']; - idOrig = idOrig.replace(/-/g, "+").replace(/_/g, "/"); - let idOrigDecoded = atob(idOrig); - let idArray = Uint8Array.from(idOrigDecoded, c => c.charCodeAt(0)) - publicKey.excludeCredentials[i]['id'] = idArray; +window.lam.webauthn.register = function(publicKey, successCallback, errorCallback) { + if (!(publicKey.challenge instanceof Uint8Array)) { + publicKey.challenge = Uint8Array.from(window.atob(publicKey.challenge), c=>c.charCodeAt(0)); + publicKey.user.id = Uint8Array.from(window.atob(publicKey.user.id), c=>c.charCodeAt(0)); + publicKey.rp.icon = window.location.href.substring(0, window.location.href.lastIndexOf("/")) + publicKey.rp.icon; + if (publicKey.excludeCredentials) { + for (let i = 0; i < publicKey.excludeCredentials.length; i++) { + let idOrig = publicKey.excludeCredentials[i]['id']; + idOrig = idOrig.replace(/-/g, "+").replace(/_/g, "/"); + let idOrigDecoded = atob(idOrig); + let idArray = Uint8Array.from(idOrigDecoded, c => c.charCodeAt(0)) + publicKey.excludeCredentials[i]['id'] = idArray; + } } } navigator.credentials.create({publicKey}) .then(function (data) { - let publicKeyCredential = { + const publicKeyCredential = { id: data.id, type: data.type, rawId: window.lam.webauthn.arrayToBase64String(new Uint8Array(data.rawId)), @@ -1447,22 +1477,10 @@ window.lam.webauthn.register = function(publicKey) { attestationObject: window.lam.webauthn.arrayToBase64String(new Uint8Array(data.response.attestationObject)) } }; - let form = jQuery("#2faform"); - let response = btoa(JSON.stringify(publicKeyCredential)); - form.append(''); - form.submit(); + successCallback(publicKeyCredential); }, function (error) { console.log(error.message); - let errorDiv = jQuery('#generic-webauthn-error'); - let buttonLabel = errorDiv.data('button'); - let dialogTitle = errorDiv.data('title'); - errorDiv.text(error.message); - window.lam.dialog.showMessage(dialogTitle, - buttonLabel, - 'generic-webauthn-error', - function () { - jQuery('#btn_logout').click(); - }); + errorCallback(error); }); } @@ -1598,7 +1616,16 @@ window.lam.webauthn.removeDevice = function(event) { window.lam.webauthn.removeOwnDevice = function(event) { event.preventDefault(); const element = jQuery(event.target); - window.lam.webauthn.removeDeviceDialog(element, 'webauthnOwnDevices'); + const successCallback = function () { + const form = jQuery("#webauthnform"); + jQuery('').attr({ + type: 'hidden', + name: 'removed', + value: 'true' + }).appendTo(form); + form.submit(); + }; + window.lam.webauthn.removeDeviceDialog(element, 'webauthnOwnDevices', successCallback); return false; } @@ -1607,15 +1634,16 @@ window.lam.webauthn.removeOwnDevice = function(event) { * * @param element delete button * @param action action for request (delete|deleteOwn) + * @param successCallback callback if all was fine (optional) */ -window.lam.webauthn.removeDeviceDialog = function(element, action) { +window.lam.webauthn.removeDeviceDialog = function(element, action, successCallback) { const dialogTitle = element.data('dialogtitle'); const okText = element.data('oktext'); const cancelText = element.data('canceltext'); let buttonList = {}; buttonList[okText] = function() { jQuery('#webauthnDeleteConfirm').dialog('close'); - window.lam.webauthn.sendRemoveDeviceRequest(element, action); + window.lam.webauthn.sendRemoveDeviceRequest(element, action, successCallback); }; buttonList[cancelText] = function() { jQuery(this).dialog("close"); @@ -1634,8 +1662,9 @@ window.lam.webauthn.removeDeviceDialog = function(element, action) { * * @param element button element * @param action action (delete|deleteOwn) + * @param successCallback callback if all was fine (optional) */ -window.lam.webauthn.sendRemoveDeviceRequest = function(element, action) { +window.lam.webauthn.sendRemoveDeviceRequest = function(element, action, successCallback) { const dn = element.data('dn'); const credential = element.data('credential'); const resultDiv = jQuery('#webauthn_results'); @@ -1653,13 +1682,51 @@ window.lam.webauthn.sendRemoveDeviceRequest = function(element, action) { data: data }) .done(function(jsonData) { - resultDiv.html(jsonData.content); + if (successCallback) { + successCallback(); + } + else { + resultDiv.html(jsonData.content); + } }) .fail(function() { console.log('Webauthn device deletion failed'); }); } +/** + * Registers a user's own webauthn device. + * + * @param event click event + */ +window.lam.webauthn.registerOwnDevice = function(event) { + event.preventDefault(); + const element = jQuery(event.target); + const dn = element.data('dn'); + const tokenValue = element.data('sec_token_value'); + const tokenName = element.data('sec_token_name'); + const publicKey = element.data('publickey'); + const successCallback = function (publicKeyCredential) { + const form = jQuery("#webauthnform"); + const response = btoa(JSON.stringify(publicKeyCredential)); + const registrationData = jQuery('#registrationData'); + registrationData.val(response); + form.submit(); + }; + const errorCallback = function (error) { + let errorDiv = jQuery('#generic-webauthn-error'); + let buttonLabel = errorDiv.data('button'); + let dialogTitle = errorDiv.data('title'); + errorDiv.text(error.message); + window.lam.dialog.showMessage(dialogTitle, + buttonLabel, + 'generic-webauthn-error' + ); + }; + window.lam.webauthn.register(publicKey, successCallback, errorCallback); + return false; +} + jQuery(document).ready(function() { window.lam.gui.equalHeight(); window.lam.form.autoTrim(); diff --git a/lam/templates/misc/ajax.php b/lam/templates/misc/ajax.php index 3cda46f1..4e86477b 100644 --- a/lam/templates/misc/ajax.php +++ b/lam/templates/misc/ajax.php @@ -342,7 +342,7 @@ class Ajax { } if ($action === 'delete') { $credentialId = $_POST['credentialId']; - $this->manageWebauthnDevicesDelete($dn, $credentialId); + $this->manageWebauthnDevicesDelete($sessionDn, $credentialId); } } diff --git a/lam/templates/tools/webauthn.php b/lam/templates/tools/webauthn.php index 3b81b592..fb07bdda 100644 --- a/lam/templates/tools/webauthn.php +++ b/lam/templates/tools/webauthn.php @@ -1,14 +1,18 @@ '; -echo "
\n"; +echo "\n"; $tabindex = 1; $container = new htmlResponsiveRow(); $container->add(new htmlTitle(_("Webauthn devices")), 12); +$webauthnManager = new WebauthnManager(); + $userDn = $_SESSION['ldap']->getUserName(); $database = new PublicKeyCredentialSourceRepositorySQLite(); -$results = $database->searchDevices($userDn); +showRemoveMessage($container); +addNewDevice($container, $webauthnManager); $container->addVerticalSpacer('0.5rem'); +$container->add(new htmlHiddenInput('registrationData', ''), 12); +$errorMessageDiv = new htmlDiv('generic-webauthn-error', new htmlOutputText('')); +$errorMessageDiv->addDataAttribute('button', _('Ok')); +$errorMessageDiv->addDataAttribute('title', _('Webauthn failed')); +$container->add($errorMessageDiv, 12); $buttonGroup = new htmlGroup(); +$registerButton = new htmlButton('register', _('Register new device')); +$registerButton->addDataAttribute('dn', $userDn); +$registerButton->addDataAttribute('sec_token_value', getSecurityTokenValue()); +$registerButton->addDataAttribute('sec_token_name', getSecurityTokenName()); +$registration = $webauthnManager->getRegistrationObject($userDn, false); +$registrationJson = json_encode($registration); +$_SESSION['webauthn_registration'] = $registrationJson; +$registerButton->addDataAttribute('publickey', $registrationJson); +$registerButton->setIconClass('createButton'); +$registerButton->setOnClick('window.lam.webauthn.registerOwnDevice(event);'); +$buttonGroup->addElement($registerButton); +$buttonGroup->addElement(new htmlSpacer('1rem', null)); $reloadButton = new htmlButton('reload', _('Reload')); $reloadButton->setIconClass('refreshButton'); $buttonGroup->addElement($reloadButton); $container->add($buttonGroup, 12); $container->addVerticalSpacer('2rem'); +$results = $database->searchDevices($userDn); if (empty($results)) { $container->add(new htmlStatusMessage('INFO', _('No devices found.')), 12); } @@ -107,6 +133,7 @@ $container->addVerticalSpacer('2rem'); $confirmationDiv = new htmlDiv('webauthnDeleteConfirm', new htmlOutputText(_('Do you really want to remove this device?')), array('hidden')); $container->add($confirmationDiv, 12); +addSecurityTokenToMetaHTML($container); parseHtml(null, $container, array(), false, $tabindex, 'user'); @@ -114,4 +141,34 @@ echo '
'; echo ''; include __DIR__ . '/../../lib/adminFooter.inc'; -?> +/** + * Checks if a new device should be registered and adds it. + * + * @param htmlResponsiveRow $container row + * @param WebauthnManager $webauthnManager webauthn manager + */ +function addNewDevice($container, $webauthnManager) { + if (empty($_POST['registrationData'])) { + return; + } + $registrationData = base64_decode($_POST['registrationData']); + $registrationObject = PublicKeyCredentialCreationOptions::createFromString($_SESSION['webauthn_registration']); + $success = $webauthnManager->storeNewRegistration($registrationObject, $registrationData); + if ($success) { + $container->add(new htmlStatusMessage('INFO', _('The device was registered.')), 12); + } + else { + $container->add(new htmlStatusMessage('ERROR', _('The device failed to register.')), 12); + } +} + +/** + * Shows the message if a device was removed. + * + * @param htmlResponsiveRow $container row + */ +function showRemoveMessage($container) { + if (!empty($_POST['removed']) && ($_POST['removed'] === 'true')) { + $container->add(new htmlStatusMessage('INFO', _('The device was deleted.')), 12); + } +} From 0990d61507e3805bfed7a2b98ade57f22db22be5 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Mon, 6 Jan 2020 14:02:32 +0100 Subject: [PATCH 42/51] webauthn --- .../manual-sources/chapter-configuration.xml | 3 ++- lam/docs/manual-sources/chapter-tools.xml | 14 ++++++++++++++ .../manual-sources/images/tool_webauthn1.png | Bin 0 -> 63628 bytes 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 lam/docs/manual-sources/images/tool_webauthn1.png diff --git a/lam/docs/manual-sources/chapter-configuration.xml b/lam/docs/manual-sources/chapter-configuration.xml index 00fbccb8..dba4bed7 100644 --- a/lam/docs/manual-sources/chapter-configuration.xml +++ b/lam/docs/manual-sources/chapter-configuration.xml @@ -271,7 +271,8 @@ devices then LAM will ask for registration on next login. Note: You cannot add any device here. This can only be done by the - user during login or self service. + user during login, webauthn tool or + self service. diff --git a/lam/docs/manual-sources/chapter-tools.xml b/lam/docs/manual-sources/chapter-tools.xml index 9efe0758..436d8853 100644 --- a/lam/docs/manual-sources/chapter-tools.xml +++ b/lam/docs/manual-sources/chapter-tools.xml @@ -420,6 +420,20 @@
+
+ Webauthn devices + + Here you can manage your webauthn/FIDO2 devices. + + You can register additional security devices and remove old ones. If + no more device is registered then LAM will ask you for registration on + next login. + + + + +
+
Tests diff --git a/lam/docs/manual-sources/images/tool_webauthn1.png b/lam/docs/manual-sources/images/tool_webauthn1.png new file mode 100644 index 0000000000000000000000000000000000000000..c82e949f7de7e602f820542109166bf364fd5ea0 GIT binary patch literal 63628 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
p;?s3HDrad^(6T7Iyvq2 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 literal 0 HcmV?d00001 From c29be12a9efb7aced4fe704fb7350eaac495f90e Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Wed, 8 Jan 2020 20:38:26 +0100 Subject: [PATCH 43/51] webauthn --- lam/lib/2factor.inc | 7 ++++--- lam/lib/modules/.gitignore | 1 + lam/lib/selfService.inc | 4 +++- lam/lib/webauthn.inc | 5 ++++- lam/templates/lib/500_lam.js | 11 +++++++---- lam/templates/misc/ajax.php | 7 ++++++- 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/lam/lib/2factor.inc b/lam/lib/2factor.inc index a23497bf..c04bf665 100644 --- a/lam/lib/2factor.inc +++ b/lam/lib/2factor.inc @@ -17,7 +17,7 @@ use \Webauthn\PublicKeyCredentialCreationOptions; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2017 - 2019 Roland Gruber + Copyright (C) 2017 - 2020 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -549,6 +549,7 @@ class WebauthnProvider extends BaseProvider { } $row->addVerticalSpacer('2rem'); $pathPrefix = $this->config->isSelfService ? '../' : ''; + $selfServiceParam = $this->config->isSelfService ? 'true' : 'false'; $row->add(new htmlImage($pathPrefix . '../graphics/webauthn.svg'), 12); $row->addVerticalSpacer('1rem'); $registerButton = new htmlButton('register_webauthn', _('Register new key')); @@ -570,7 +571,7 @@ class WebauthnProvider extends BaseProvider { $errorMessageDiv->addDataAttribute('button', _('Ok')); $errorMessageDiv->addDataAttribute('title', _('Webauthn failed')); $row->add($errorMessageDiv, 12); - $row->add(new htmlJavaScript('window.lam.webauthn.start(\'' . $pathPrefix . '\');'), 0); + $row->add(new htmlJavaScript('window.lam.webauthn.start(\'' . $pathPrefix . '\', ' . $selfServiceParam . ');'), 0); } /** @@ -594,7 +595,7 @@ class WebauthnProvider extends BaseProvider { $userDn = $_SESSION['ldap']->getUserName(); } else { - $userDn = $_SESSION['selfService_clientDN']; + $userDn = lamDecrypt($_SESSION['selfService_clientDN'], 'SelfService'); } $hasTokens = $webauthnManager->isRegistered($userDn); if (!$hasTokens) { diff --git a/lam/lib/modules/.gitignore b/lam/lib/modules/.gitignore index 4db515d8..4e8cabc6 100644 --- a/lam/lib/modules/.gitignore +++ b/lam/lib/modules/.gitignore @@ -44,3 +44,4 @@ /nPosixGroup.inc /nPosixUser.inc /bindDLZXfr.inc +/webauthn.inc diff --git a/lam/lib/selfService.inc b/lam/lib/selfService.inc index bbf379aa..7d8b63c9 100644 --- a/lam/lib/selfService.inc +++ b/lam/lib/selfService.inc @@ -3,7 +3,7 @@ use \LAM\LIB\TWO_FACTOR\TwoFactorProviderService; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2006 - 2019 Roland Gruber + Copyright (C) 2006 - 2020 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -452,6 +452,7 @@ class selfServiceProfile { public $twoFactorAuthenticationClientId = ''; public $twoFactorAuthenticationSecretKey = ''; public $twoFactorAuthenticationAttribute = 'uid'; + public $twoFactorAuthenticationDomain = ''; /** provider for captcha (-/google) */ public $captchaProvider = '-'; @@ -512,6 +513,7 @@ class selfServiceProfile { $this->twoFactorAuthenticationClientId = ''; $this->twoFactorAuthenticationSecretKey = ''; $this->twoFactorAuthenticationAttribute = 'uid'; + $this->twoFactorAuthenticationDomain = ''; $this->captchaProvider = '-'; $this->reCaptchaSiteKey = ''; $this->reCaptchaSecretKey = ''; diff --git a/lam/lib/webauthn.inc b/lam/lib/webauthn.inc index e396fec1..a41d0f34 100644 --- a/lam/lib/webauthn.inc +++ b/lam/lib/webauthn.inc @@ -185,7 +185,10 @@ class WebauthnManager { private function createRpEntry($isSelfService) { $pathPrefix = $isSelfService ? '../' : ''; $icon = $pathPrefix . '../graphics/logo136.png'; - if (!$isSelfService) { + if ($isSelfService) { + $domain = $_SESSION['selfServiceProfile']->twoFactorAuthenticationDomain; + } + else { $domain = $_SESSION['config']->getTwoFactorAuthenticationDomain(); } return new PublicKeyCredentialRpEntity( diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index 2d716ede..8b5b4d31 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -1375,11 +1375,12 @@ window.lam.webauthn = window.lam.webauthn || {}; * Starts the webauthn process. * * @param prefix path prefix for Ajax endpoint + * @param isSelfService runs as part of self service */ -window.lam.webauthn.start = function(prefix) { +window.lam.webauthn.start = function(prefix, isSelfService) { jQuery(document).ready( function() { - window.lam.webauthn.run(prefix); + window.lam.webauthn.run(prefix, isSelfService); } ); } @@ -1388,8 +1389,9 @@ window.lam.webauthn.start = function(prefix) { * Checks if the user is registered and starts login/registration. * * @param prefix path prefix for Ajax endpoint + * @param isSelfService runs as part of self service */ -window.lam.webauthn.run = function(prefix) { +window.lam.webauthn.run = function(prefix, isSelfService) { jQuery('#btn_skip_webauthn').click(function () { let form = jQuery("#2faform"); form.append(''); @@ -1408,8 +1410,9 @@ window.lam.webauthn.run = function(prefix) { jsonInput: '', sec_token: token }; + const extraParam = isSelfService ? '&selfservice=true' : ''; jQuery.ajax({ - url: prefix + 'misc/ajax.php?function=webauthn', + url: prefix + 'misc/ajax.php?function=webauthn' + extraParam, method: 'POST', data: data }) diff --git a/lam/templates/misc/ajax.php b/lam/templates/misc/ajax.php index 4e86477b..3dd9df2e 100644 --- a/lam/templates/misc/ajax.php +++ b/lam/templates/misc/ajax.php @@ -202,7 +202,12 @@ class Ajax { */ private function manageWebauthn($isSelfService) { include_once __DIR__ . '/../../lib/webauthn.inc'; - $userDN = $_SESSION['ldap']->getUserName(); + if ($isSelfService) { + $userDN = lamDecrypt($_SESSION['selfService_clientDN'], 'SelfService'); + } + else { + $userDN = $_SESSION['ldap']->getUserName(); + } $webauthnManager = new WebauthnManager(); $isRegistered = $webauthnManager->isRegistered($userDN); if (!$isRegistered) { From 3ac7ae668bda51c5cec898fc1c5fc305a6ac5465 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Fri, 10 Jan 2020 20:05:06 +0100 Subject: [PATCH 44/51] data attributes --- lam/lib/html.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lam/lib/html.inc b/lam/lib/html.inc index 52daa4db..e68e5a33 100644 --- a/lam/lib/html.inc +++ b/lam/lib/html.inc @@ -2,7 +2,7 @@ /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2010 - 2019 Roland Gruber + Copyright (C) 2010 - 2020 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -3392,7 +3392,7 @@ class htmlLink extends htmlElement { if (sizeof($this->cssClasses) > 0) { $classAttr = ' class="' . implode(' ', $this->cssClasses) . '"'; } - echo '' . $image . $text . ''; + echo 'getDataAttributesAsString() . '>' . $image . $text . ''; if ($this->showAsButton) { echo '