Subversion Repositories php_utils

Compare Revisions

No changes between revisions

Regard whitespace Rev 1 → Rev HEAD

/trunk/LICENSE
0,0 → 1,202
 
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
 
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 
1. Definitions.
 
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
 
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
 
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
 
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
 
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
 
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
 
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
 
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
 
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
 
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
 
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
 
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
 
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
 
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
 
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
 
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
 
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
 
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
 
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
 
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
 
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
 
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
 
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
 
END OF TERMS AND CONDITIONS
 
APPENDIX: How to apply the Apache License to your work.
 
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
 
Copyright 2018 Daniel Marschall, ViaThinkSoft
 
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
/trunk/README.md
0,0 → 1,5
 
# PHP Utils: Various methods for PHP
 
This repository contains various helpful PHP utilities,
to handle UUIDs, IPv4, OIDs, etc.
/trunk/SecureMailer.class.php
0,0 → 1,134
<?php
 
/*
* Secure Mailer PHP Class
* Copyright 2009-2013 Daniel Marschall, ViaThinkSoft
* QB_SECURE_MAIL_PARAM (C) Erich Kachel
* Version 2013-04-14
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
// TODO: getHeaders() as single string , attachments , remove headers etc, headers as array in/out, do you also need addRawHeader()?
 
class SecureMailer {
private $headers = '';
 
// TODO: This should rather be private, but it won't work
const endl = "\n"; // GMX doesn't like CRLF! wtf?! (tested in Postfix in Linux)
 
private function QB_SECURE_MAIL_PARAM($param_ = '', $level_ = 2) {
// Prevents eMail header injections
// Source: http://www.erich-kachel.de/?p=26 (modified)
 
/* replace until done */
$filtered = null;
while (is_null($filtered) || ($param_ != $filtered)) {
if (!is_null($filtered)) {
$param_ = $filtered;
}
 
$filtered = preg_replace("/(Content-Transfer-Encoding:|MIME-Version:|content-type:|Subject:|to:|cc:|bcc:|from:|reply-to:)/ims", '', $param_);
}
 
unset($filtered);
 
if ($level_ >= 2) {
/* replace until done */
while (!isset($filtered) || ($param_ != $filtered)) {
if (isset($filtered)) {
$param_ = $filtered;
}
 
$filtered = preg_replace("/(%0A|\\\\r|%0D|\\\\n|%00|\\\\0|%09|\\\\t|%01|%02|%03|%04|%05|%06|%07|%08|%09|%0B|%0C|%0E|%0F|%10|%11|%12|%13)/ims", '', $param_);
}
}
 
return $param_;
}
 
private function getHeaders() {
return $this->headers;
}
 
private static function mail_base64_encode($text) {
// Why 72? Seen here: http://linux.dsplabs.com.au/munpack-mime-base64-multi-part-attachment-php-perl-decode-email-pdf-p82/
return wordwrap(base64_encode($text), 72, self::endl, true);
}
 
private function headerLine($name, $value) {
// Change 2011-02-09
// LF is OK! CRLF does lead to CR+CRLF on some systems!
// http://bugs.php.net/bug.php?id=15841
// The mail() function is not talking to an SMTP server, so RFC2822 does not apply here. mail() is talking to a command line program on the local system, and it is reasonable to expect that program to require system-native line breaks.
return $this->QB_SECURE_MAIL_PARAM($name).': '.$this->QB_SECURE_MAIL_PARAM($value)."\n";
}
 
public function addHeader($name, $value) {
$this->headers .= $this->headerLine($name, $value);
}
 
public static function utf8Subject($subject) {
$subject = mb_convert_encoding($subject, 'UTF-8');
return '=?UTF-8?B?'.base64_encode($subject).'?=';
}
 
private function _sendMail($recipient, $subject, $message, $add_headers='') {
return @mail(
$this->QB_SECURE_MAIL_PARAM($recipient),
$this->QB_SECURE_MAIL_PARAM($subject),
$this->QB_SECURE_MAIL_PARAM($message, 1),
$this->getHeaders().$add_headers
);
}
 
public function sendMail($recipient, $subject, $message) {
return $this->_sendMail($recipient, $subject, $message, '');
}
 
// TODO: generate plain from html (strip tags), optional
public function sendMailHTMLandPlainMultipart($to, $subject, $msg_html, $msg_plain) {
$boundary = uniqid('np');
 
$msg_html = $this->QB_SECURE_MAIL_PARAM($msg_html, 1);
$msg_plain = $this->QB_SECURE_MAIL_PARAM($msg_plain, 1);
 
$add_headers = $this->headerLine('MIME-Version', '1.0');
$add_headers .= $this->headerLine('Content-Type', 'multipart/alternative; boundary="'.$boundary.'"');
 
$message = "This is a MIME encoded message.";
$message .= self::endl;
$message .= self::endl;
$message .= "--" . $boundary . self::endl;
$message .= "Content-type: text/plain; charset=utf-8".self::endl;
$message .= "Content-Transfer-Encoding: base64".self::endl;
$message .= self::endl;
$message .= $this->mail_base64_encode($msg_plain); // better than wordwrap&quoted-printable because of long lines (e.g. links)
$message .= self::endl;
$message .= self::endl;
$message .= "--" . $boundary . self::endl;
$message .= "Content-type: text/html; charset=utf-8".self::endl;
$message .= "Content-Transfer-Encoding: base64".self::endl;
$message .= self::endl;
$message .= $this->mail_base64_encode($msg_html);
$message .= self::endl;
$message .= self::endl."--" . $boundary . "--";
 
return @mail(
$this->QB_SECURE_MAIL_PARAM($to),
$this->QB_SECURE_MAIL_PARAM($subject),
$message,
$this->getHeaders().$add_headers
);
}
}
/trunk/VtsBrowserDownload.class.php
0,0 → 1,158
<?php
 
/*
* VtsBrowserDownload.class.php
* Copyright 2021 Daniel Marschall, ViaThinkSoft
* Revision: 2021-05-21
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
class VtsBrowserDownload {
 
private static function wellKnownInlineFile($file_extension) {
// Windows Firefox: Browser decided wheater to display or download by looking at the mime type (inline disposition with explicit filename works)
// Windows Chrome: Browser decided wheater to display or download by looking at the mime type (inline disposition with explicit filename DOES NOT WORK)
 
//$file_extension = strtolower($file_extension);
//$array_listen = array('txt', 'mp3', 'wav', 'mid', 'ogg', 'pdf', 'avi', 'mov', 'mp4', 'mpeg', 'mpg', 'swf', 'gif', 'jpg', 'jpeg', 'png');
//return in_array($file_extension, $array_listen);
 
return true;
}
 
private static function getMimeType($file_extension) {
$file_extension = strtolower($file_extension);
if (!class_exists('VtsFileTypeDetect')) {
// https://github.com/danielmarschall/fileformats
throw new Exception("Require 'fileformats' package");
}
return VtsFileTypeDetect::getMimeType('dummy.'.$file_extension);
}
 
public static function output_file($file, $mime_type='', $inline_mode=2/*2=auto*/) {
// Partitally taken from:
// - https://stackoverflow.com/a/13821992/488539
// - https://stackoverflow.com/a/32885706/488539
 
if (connection_status() != 0) return false;
 
$file_extension = pathinfo($file, PATHINFO_EXTENSION);
 
if(!is_readable($file)) throw new Exception('File not found or inaccessible!');
$size = filesize($file);
$name = rawurldecode(basename($file));
 
if ($mime_type == '') {
$mime_type = self::getMimeType($file_extension);
if (!$mime_type) $mime_type='application/force-download';
}
 
 
 
while (ob_get_level() > 0) @ob_end_clean();
 
switch ($inline_mode) {
 
case 0:
$disposition = 'attachment';
break;
 
case 1:
$disposition = 'inline';
break;
 
case 2:
$disposition = self::wellKnownInlineFile($file_extension) ? 'inline' : 'attachment';
break;
 
default:
throw new Exception('Invalid value for inline_mode');
}
 
if(ini_get('zlib.output_compression')){
ini_set('zlib.output_compression', 'Off');
}
header('Content-Type: ' . $mime_type);
 
$ua = isset($_SERVER['HTTP_USER_AGENT']) ? strtoupper($_SERVER['HTTP_USER_AGENT']) : '';
if (strstr($ua, 'MSIE')) {
$name_msie = preg_replace('/\./', '%2e', $name, substr_count($name, '.') - 1);
header('Content-Disposition: '.$disposition.';filename="'.$name_msie.'"');
} else if (strstr($ua, 'FIREFOX')) {
// TODO: Implement "encodeRFC5987ValueChars" described at https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent ?
$name_utf8 = mb_convert_encoding($name, 'UTF-8');
header('Content-Disposition: '.$disposition.';filename*="UTF-8\'\''.$name_utf8.'"');
} else {
// Note: There is possibly a bug in Google Chrome: https://stackoverflow.com/questions/61866508/chrome-ignores-content-disposition-filename
header('Content-Disposition: '.$disposition.';filename="'.$name.'"');
}
 
header('Content-Transfer-Encoding: binary');
header('Accept-Ranges: bytes');
header('Cache-Control: public');
 
if (isset($_SERVER['HTTP_RANGE'])) {
list($a, $range) = explode("=",$_SERVER['HTTP_RANGE'],2);
list($range) = explode(",",$range,2);
list($range, $range_end) = explode("-", $range);
$range=intval($range);
if(!$range_end) {
$range_end=$size-1;
} else {
$range_end=intval($range_end);
}
 
$new_length = $range_end-$range+1;
http_response_code(206); // 206 Partial Content
header("Content-Length: $new_length");
header("Content-Range: bytes $range-$range_end/$size");
} else {
$range = 0;
$etag = md5_file($file);
header("Etag: $etag");
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && (trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag)) {
http_response_code(304); // 304 Not Modified
return true;
}
 
$new_length=$size;
header("Content-Length: ".$size);
header('Content-MD5: '.$etag); // RFC 2616 clause 14.15
}
 
set_time_limit(0);
 
$chunksize = 1*(1024*1024);
$bytes_send = 0;
if ($file = fopen($file, 'r')) {
if(isset($_SERVER['HTTP_RANGE']))
fseek($file, $range);
 
while(!feof($file) &&
(!connection_aborted()) && // connection_status() == 0
($bytes_send<$new_length))
{
$buffer = fread($file, $chunksize);
echo($buffer);
flush();
$bytes_send += strlen($buffer);
}
fclose($file);
} else {
throw new Exception("Cannot open file $file");
}
return((connection_status() == 0) and !connection_aborted());
}
 
}
/trunk/VtsLDAPUtils.class.php
0,0 → 1,128
<?php
 
/*
* VtsLDAPUtils - Simple LDAP helper functions
* Copyright 2021 - 2023 Daniel Marschall, ViaThinkSoft
* Revision: 2023-11-30
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
class VtsLDAPUtils {
 
protected $conn = null;
 
private static function _L(string $str, ...$sprintfArgs): string {
if (function_exists('_L')) {
return _L($str, $sprintfArgs);
} else if (function_exists('my_vsprintf')) {
return my_vsprintf($str, $sprintfArgs);
} else {
$n = 1;
foreach ($sprintfArgs as $val) {
$str = str_replace("%$n", $val, $str);
$n++;
}
$str = str_replace("%%", "%", $str);
return $str;
}
}
 
public static function getString($ldap_userinfo, $attributeName) {
$ary = self::getArray($ldap_userinfo, $attributeName);
return implode("\n", $ary);
}
 
public static function getArray($ldap_userinfo, $attributeName) {
$ary = array();
if (isset($ldap_userinfo[$attributeName])) {
$cnt = $ldap_userinfo[$attributeName]['count'];
for ($i=0; $i<$cnt; $i++) {
$ary[] = $ldap_userinfo[$attributeName][$i];
}
}
return $ary;
}
 
public function isMemberOfRec($userDN, $groupDN) {
 
if (isset($userDN['dn'])) $userDN = $userDN['dn'];
if (isset($groupDN['dn'])) $groupDN = $groupDN['dn'];
 
if (!$this->conn) throw new Exception('LDAP not connected');
$res = ldap_read($this->conn, $groupDN, "(objectClass=*)");
if (!$res) return false;
$entries = ldap_get_entries($this->conn, $res);
if (!isset($entries[0])) return false;
if (!isset($entries[0]['member'])) return false;
if (!isset($entries[0]['member']['count'])) return false;
$cntMember = $entries[0]['member']['count'];
for ($iMember=0; $iMember<$cntMember; $iMember++) {
$groupOrUser = $entries[0]['member'][$iMember];
if (strtolower($groupOrUser) == strtolower($userDN)) return true;
if ($this->isMemberOfRec($userDN, $groupOrUser)) return true;
}
return false;
}
 
public function __destruct() {
$this->disconnect();
}
 
public function disconnect() {
if ($this->conn) {
//ldap_unbind($this->conn); // commented out because ldap_unbind() kills the link descriptor
ldap_close($this->conn);
$this->conn = null;
}
}
 
public function connect($cfg_ldap_server, $cfg_ldap_port=389) {
$this->disconnect();
 
// Connect to the server
if (strpos($cfg_ldap_server, '://') !== false) {
// e.g. ldap://hostname:port or ldaps://hostname:port
$uri = $cfg_ldap_server;
} else {
$secure = ($cfg_ldap_port == 636) || ($cfg_ldap_port == 3268) || ($cfg_ldap_port == 3269);
$schema = $secure ? 'ldaps' : 'ldap';
$uri = $schema . '://' . $cfg_ldap_server . ':' . $cfg_ldap_port;
}
if (!($ldapconn = @ldap_connect($uri))) throw new Exception(self::_L('Cannot connect to LDAP server'));
 
ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, 0);
 
$this->conn = $ldapconn;
}
 
public function login($username, $password) {
return @ldap_bind($this->conn, $username, $password);
}
 
public function getUserInfo($userPrincipalName, $cfg_ldap_base_dn) {
$cfg_ldap_user_filter = "(&(objectClass=user)(objectCategory=person)(userPrincipalName=".ldap_escape($userPrincipalName, '', LDAP_ESCAPE_FILTER)."))";
 
if (!($result = @ldap_search($this->conn,$cfg_ldap_base_dn, $cfg_ldap_user_filter))) throw new Exception(self::_L('Error in search query: %1', ldap_error($this->conn)));
$data = ldap_get_entries($this->conn, $result);
$ldap_userinfo = array();
 
if ($data['count'] == 0) return false; /* @phpstan-ignore-line */
$ldap_userinfo = $data[0];
 
// empty($ldap_userinfo) can happen if the user did not log-in using their correct userPrincipalName (e.g. "username@domainname" instead of "username@domainname.local")
return empty($ldap_userinfo) ? false : $ldap_userinfo;
}
 
}
/trunk/aid_decoder.inc.php
0,0 → 1,922
<?php
 
/*
* ISO/IEC 7816 Application Identifier decoder for PHP
* Copyright 2022 Daniel Marschall, ViaThinkSoft
* Version 2022-09-20
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
// Definition of Application Identifiers (AID):
// - ISO 7816-05:1994 (1st ed.), clause 5.2
// - ISO 7816-04:2005 (2nd ed.), clause 8.2.1.2, Annex A.1, Annex D
// - ISO 7816-04:2013 (3rd ed.), clause 12.2.3, Annex A.1, Annex D
// - ISO 7816-04:2020 (4th ed.), clause 12.3.4, Annex A.1, Annex D
 
include_once __DIR__ . '/gmp_supplement.inc.php';
include_once __DIR__ . '/misc_functions.inc.php';
 
# ---
 
/*
#test2('A000000051AABBCC'); // International Registration
#test2('B01234567890'); // Illegal AID (RFU)
#test2('D276000098AABBCCDDEEFFAABBCCDDEE'); // National Registration
#test2('F01234567890'); // Unregistered AID
#test('91234FFF999'); // IIN based AID
#test('51234FFF999'); // IIN based AID
test('E828BD080F014E585031'); // ISO E8-OID 1.0.aaaa
test('E80704007F00070304'); // BSI Illegal E8-OID-AID (with DER Length)
test('E80704007F0007030499'); // BSI Illegal E8-OID-AID + PIX (PIX is never used by BSI; it's just for us to test)
test('E829112233'); // Possible other illegal E8-OID
 
function test2($aid) {
while ($aid != '') {
echo test($aid);
$aid = substr($aid,0,strlen($aid)-1);
}
}
 
function test($aid) {
$out = _decode_aid($aid);
$max_key_len = 32; // min width of first column
foreach ($out as $a) {
if (is_array($a)) {
$max_key_len = max($max_key_len,strlen($a[0]));
}
}
foreach ($out as $a) {
if (is_array($a)) {
echo str_pad($a[0],$max_key_len,' ',STR_PAD_RIGHT).' '.$a[1]."\n";
} else {
echo $a."\n";
}
}
echo "\n";
}
*/
 
# ---
 
function _aid_e8_oid_helper($output_oid,$by,$ii,&$ret,$minmax_measure,$min,$max) {
if ($minmax_measure == 'ARC') {
if (($min!=-1) && (count($output_oid)<$min)) return true; // continue
if (($max!=-1) && (count($output_oid)>$max)) return false; // stop
} else if ($minmax_measure == 'DER') {
if (($min!=-1) && ($ii+1<$min)) return true; // continue
if (($max!=-1) && ($ii+1>$max)) return false; // stop
}
 
$byy = $by;
for ($i=0;$i<=$ii;$i++) array_shift($byy);
 
$is_iso_standard = (count($output_oid) >= 3) && ($output_oid[0] == '1') && ($output_oid[1] == '0');
 
$s_oid = implode('.',$output_oid);
 
if ($is_iso_standard) {
$std_hf = 'Standard ISO/IEC '.$output_oid[2];
if (isset($output_oid[3])) $std_hf .= "-".$output_oid[3];
} else {
$std_hf = "Unknown Standard"; // should not happen
}
 
$pix = implode(':',$byy);
 
if ($pix !== '') {
$ret[] = array($ii+1,"$std_hf (OID $s_oid)",$pix);
} else {
$ret[] = array($ii+1,"$std_hf (OID $s_oid)","");
}
 
return true;
}
 
function _aid_e8_interpretations($pure_der, $minmax_measure='ARC', $min=-1, $max=-1) {
$ret = array();
 
$output_oid = array();
 
$pure_der = strtoupper(str_replace(' ','',$pure_der));
$pure_der = strtoupper(str_replace(':','',$pure_der));
$by = str_split($pure_der,2);
 
// The following part is partially taken from the DER decoder/encoder by Daniel Marschall:
// https://github.com/danielmarschall/oidconverter/blob/master/php/OidDerConverter.class.phps
// (Only the DER "value" part, without "type" and "length")
 
$part = 2; // DER part 0 (type) and part 1 (length) not present
$fSub = 0; // Subtract value from next number output. Used when encoding {2 48} and up
$ll = gmp_init(0);
$arcBeginning = true;
 
foreach ($by as $ii => $pb) {
 
$pb = hexdec($pb);
 
if ($part == 2) { // First two arcs
$first = floor($pb / 40);
$second = $pb % 40;
if ($first > 2) {
$first = 2;
$output_oid[] = $first;
if (!_aid_e8_oid_helper($output_oid, $by, $ii, $ret, $minmax_measure, $min, $max)) break;
$arcBeginning = true;
 
if (($pb & 0x80) != 0) {
// 2.48 and up => 2+ octets
// Output in "part 3"
 
if ($pb == 0x80) {
throw new Exception("Encoding error. Illegal 0x80 paddings. (See Rec. ITU-T X.690, clause 8.19.2)\n");
} else {
$arcBeginning = false;
}
 
$ll = gmp_add($ll, ($pb & 0x7F));
$fSub = 80;
$fOK = false;
} else {
// 2.0 till 2.47 => 1 octet
$second = $pb - 80;
$output_oid[] = $second;
if (!_aid_e8_oid_helper($output_oid, $by, $ii, $ret, $minmax_measure, $min, $max)) break;
$arcBeginning = true;
$fOK = true;
$ll = gmp_init(0);
}
} else {
// 0.0 till 0.37 => 1 octet
// 1.0 till 1.37 => 1 octet
$output_oid[] = $first;
$output_oid[] = $second;
if (!_aid_e8_oid_helper($output_oid, $by, $ii, $ret, $minmax_measure, $min, $max)) break;
$arcBeginning = true;
$fOK = true;
$ll = gmp_init(0);
}
$part++;
} else { //else if ($part == 3) { // Arc three and higher
if (($pb & 0x80) != 0) {
if ($arcBeginning && ($pb == 0x80)) {
throw new Exception("Encoding error. Illegal 0x80 paddings. (See Rec. ITU-T X.690, clause 8.19.2)");
} else {
$arcBeginning = false;
}
 
$ll = gmp_mul($ll, 0x80);
$ll = gmp_add($ll, ($pb & 0x7F));
$fOK = false;
} else {
$fOK = true;
$ll = gmp_mul($ll, 0x80);
$ll = gmp_add($ll, $pb);
$ll = gmp_sub($ll, $fSub);
$output_oid[] = gmp_strval($ll, 10);
 
if (!_aid_e8_oid_helper($output_oid, $by, $ii, $ret, $minmax_measure, $min, $max)) break;
 
// Happens only if 0x80 paddings are allowed
// $fOK = gmp_cmp($ll, 0) >= 0;
$ll = gmp_init(0);
$fSub = 0;
$arcBeginning = true;
}
}
}
 
return $ret;
}
 
function _aid_e8_length_usage($aid) {
// Return true if $aid is most likely E8+Length+OID (not intended by ISO)
// Return false if $aid is most likely E8+OID (defined by ISO for their OID 1.0)
// Return null if it is ambiguous
 
assert(substr($aid,0,2) === 'E8');
$len = substr($aid,2,2);
$rest = substr($aid,4);
$rest_num_bytes = floor(strlen($rest)/2);
 
$is_e8_len_oid = false;
$is_e8_oid = false;
 
// There are not enough following bytes, so it cannot be E8+Length+OID. It must be E8+OID
if ($len > $rest_num_bytes) $is_e8_oid = true;
 
// E8 00 ... must be E8+OID, with OID 0.0.xx (recommendation), because Length=0 is not possible
if ($len == 0) $is_e8_oid = true;
 
// E8 01 ... must be E8+Length+OID, because OID 0.1 (question) was never used
if ($len == 1) $is_e8_len_oid = true;
 
// E8 02 refers to OID 0.2 (administration) but could also refer to a length
//if ($len == 2) return null;
 
// E8 03 refers to OID 0.3 (network-operator) but could also refer to a length
//if ($len == 3) return null;
 
// E8 04 refers to OID 0.4 (identified-organization) but could also refer to a length
//if ($len == 4) return null;
 
// E8 05 refers to OID 0.5 (r-recommendation) but could also refer to a length
//if ($len == 5) return null;
 
// E8 06-08 refers to OID 0.6-8, which are not defined, E8+Length+OID
if (($len >= 6) && ($len <= 8)) $is_e8_len_oid = true;
 
// E8 09 refers to OID 0.9, which can be an OID or a Length
if ($len == 9) {
// The only legal child of OID 0.9 is OID 0.9.2342 ($len=09, $rest=9226); then it is E8+OID
// An OID beginning with DER encoding 9226 would be 2.2262, which is very unlikely
if (substr($rest,0,4) === '9226') {
// 09 92 26 is OID 0.9.2342, which is a valid OID (the only valid OID) => valid OID
// 92 26 would be OID 2.2262 which is most likely not a valid OID => invalid Len
$is_e8_oid = true;
} else {
// Any other child inside 0.9 except for 2342 is illegal, so it must be length
$is_e8_len_oid = true;
}
}
 
// E8 10-14 refers to OID 0.10-14 which is not defined. Therefore it must be E8+Length+OID
if (($len >= 10) && ($len <= 14)) $is_e8_len_oid = true;
 
// If E8+Length+OID, then Len can max be 14, because E8 takes 1 byte, length takes 1 byte, and AID must be max 16 bytes
if ($len > 14) $is_e8_oid = true;
 
// There is at least one case where the usage of E8+Length+OID is known:
// Including the DER Encoding "Length" is not defined by ISO but used illegally
// by German BSI (beside the fact that ISO never allowed anyone else to use E8-AIDs outside
// of OID arc 1.0),
// e.g. AID E80704007F00070302 defined by "BSI TR-03110" was intended to represent 0.4.0.127.0.7.3.2
// "more correct" would have been AID E804007F00070302
// AID E80704007F00070304 defined by "BSI TR-03109-2" was intended to represent 0.4.0.127.0.7.3.4
// "more correct" would have been AID E804007F00070304
if (substr($rest,0,10) == '04007F0007'/*0.4.0.127.0.7*/) $is_e8_len_oid = $len <= 14;
 
// Now conclude
if (!$is_e8_oid && $is_e8_len_oid) return true/*E8+Length+OID*/;
if ( $is_e8_oid && !$is_e8_len_oid) return false/*E8+OID*/;
return null/*ambiguous*/;
}
 
function decode_aid($aid,$compact=true) {
$sout = '';
if (strtolower(substr($aid,0,2)) == '0x') $aid = substr($aid,2);
$out = _decode_aid($aid);
if ($compact) {
$max_key_len = 0;
foreach ($out as $a) {
if (is_array($a)) {
$max_key_len = max($max_key_len,strlen($a[0]));
}
}
} else {
$max_key_len = 32; // 16 bytes
}
foreach ($out as $a) {
if (is_array($a)) {
$sout .= str_pad($a[0],$max_key_len,' ',STR_PAD_RIGHT).' '.$a[1]."\n";
} else {
$sout .= $a."\n";
}
}
return $sout;
}
 
function _is_bcd($num) {
return preg_match('@^[0-9]+$@', $num, $m);
}
 
function _decode_aid($aid) {
 
// based on https://github.com/thephpleague/iso3166/blob/main/src/ISO3166.php
// commit 26 July 2022
// Generated using:
/*
$x = new ISO3166();
$bla = $x->all();
foreach ($bla as $data) {
$out[] = "\t\$iso3166['".$data['numeric']."'] = \"".$data['name']."\";\n";
}
*/
$iso3166['004'] = "Afghanistan";
$iso3166['248'] = "Åland Islands";
$iso3166['008'] = "Albania";
$iso3166['012'] = "Algeria";
$iso3166['016'] = "American Samoa";
$iso3166['020'] = "Andorra";
$iso3166['024'] = "Angola";
$iso3166['660'] = "Anguilla";
$iso3166['010'] = "Antarctica";
$iso3166['028'] = "Antigua and Barbuda";
$iso3166['032'] = "Argentina";
$iso3166['051'] = "Armenia";
$iso3166['533'] = "Aruba";
$iso3166['036'] = "Australia";
$iso3166['040'] = "Austria";
$iso3166['031'] = "Azerbaijan";
$iso3166['044'] = "Bahamas";
$iso3166['048'] = "Bahrain";
$iso3166['050'] = "Bangladesh";
$iso3166['052'] = "Barbados";
$iso3166['112'] = "Belarus";
$iso3166['056'] = "Belgium";
$iso3166['084'] = "Belize";
$iso3166['204'] = "Benin";
$iso3166['060'] = "Bermuda";
$iso3166['064'] = "Bhutan";
$iso3166['068'] = "Bolivia (Plurinational State of)";
$iso3166['535'] = "Bonaire, Sint Eustatius and Saba";
$iso3166['070'] = "Bosnia and Herzegovina";
$iso3166['072'] = "Botswana";
$iso3166['074'] = "Bouvet Island";
$iso3166['076'] = "Brazil";
$iso3166['086'] = "British Indian Ocean Territory";
$iso3166['096'] = "Brunei Darussalam";
$iso3166['100'] = "Bulgaria";
$iso3166['854'] = "Burkina Faso";
$iso3166['108'] = "Burundi";
$iso3166['132'] = "Cabo Verde";
$iso3166['116'] = "Cambodia";
$iso3166['120'] = "Cameroon";
$iso3166['124'] = "Canada";
$iso3166['136'] = "Cayman Islands";
$iso3166['140'] = "Central African Republic";
$iso3166['148'] = "Chad";
$iso3166['152'] = "Chile";
$iso3166['156'] = "China";
$iso3166['162'] = "Christmas Island";
$iso3166['166'] = "Cocos (Keeling) Islands";
$iso3166['170'] = "Colombia";
$iso3166['174'] = "Comoros";
$iso3166['178'] = "Congo";
$iso3166['180'] = "Congo (Democratic Republic of the)";
$iso3166['184'] = "Cook Islands";
$iso3166['188'] = "Costa Rica";
$iso3166['384'] = "Côte d'Ivoire";
$iso3166['191'] = "Croatia";
$iso3166['192'] = "Cuba";
$iso3166['531'] = "Curaçao";
$iso3166['196'] = "Cyprus";
$iso3166['203'] = "Czechia";
$iso3166['208'] = "Denmark";
$iso3166['262'] = "Djibouti";
$iso3166['212'] = "Dominica";
$iso3166['214'] = "Dominican Republic";
$iso3166['218'] = "Ecuador";
$iso3166['818'] = "Egypt";
$iso3166['222'] = "El Salvador";
$iso3166['226'] = "Equatorial Guinea";
$iso3166['232'] = "Eritrea";
$iso3166['233'] = "Estonia";
$iso3166['231'] = "Ethiopia";
$iso3166['748'] = "Eswatini";
$iso3166['238'] = "Falkland Islands (Malvinas)";
$iso3166['234'] = "Faroe Islands";
$iso3166['242'] = "Fiji";
$iso3166['246'] = "Finland";
$iso3166['250'] = "France";
$iso3166['254'] = "French Guiana";
$iso3166['258'] = "French Polynesia";
$iso3166['260'] = "French Southern Territories";
$iso3166['266'] = "Gabon";
$iso3166['270'] = "Gambia";
$iso3166['268'] = "Georgia";
$iso3166['276'] = "Germany";
$iso3166['288'] = "Ghana";
$iso3166['292'] = "Gibraltar";
$iso3166['300'] = "Greece";
$iso3166['304'] = "Greenland";
$iso3166['308'] = "Grenada";
$iso3166['312'] = "Guadeloupe";
$iso3166['316'] = "Guam";
$iso3166['320'] = "Guatemala";
$iso3166['831'] = "Guernsey";
$iso3166['324'] = "Guinea";
$iso3166['624'] = "Guinea-Bissau";
$iso3166['328'] = "Guyana";
$iso3166['332'] = "Haiti";
$iso3166['334'] = "Heard Island and McDonald Islands";
$iso3166['336'] = "Holy See";
$iso3166['340'] = "Honduras";
$iso3166['344'] = "Hong Kong";
$iso3166['348'] = "Hungary";
$iso3166['352'] = "Iceland";
$iso3166['356'] = "India";
$iso3166['360'] = "Indonesia";
$iso3166['364'] = "Iran (Islamic Republic of)";
$iso3166['368'] = "Iraq";
$iso3166['372'] = "Ireland";
$iso3166['833'] = "Isle of Man";
$iso3166['376'] = "Israel";
$iso3166['380'] = "Italy";
$iso3166['388'] = "Jamaica";
$iso3166['392'] = "Japan";
$iso3166['832'] = "Jersey";
$iso3166['400'] = "Jordan";
$iso3166['398'] = "Kazakhstan";
$iso3166['404'] = "Kenya";
$iso3166['296'] = "Kiribati";
$iso3166['408'] = "Korea (Democratic People's Republic of)";
$iso3166['410'] = "Korea (Republic of)";
$iso3166['414'] = "Kuwait";
$iso3166['417'] = "Kyrgyzstan";
$iso3166['418'] = "Lao People's Democratic Republic";
$iso3166['428'] = "Latvia";
$iso3166['422'] = "Lebanon";
$iso3166['426'] = "Lesotho";
$iso3166['430'] = "Liberia";
$iso3166['434'] = "Libya";
$iso3166['438'] = "Liechtenstein";
$iso3166['440'] = "Lithuania";
$iso3166['442'] = "Luxembourg";
$iso3166['446'] = "Macao";
$iso3166['807'] = "North Macedonia";
$iso3166['450'] = "Madagascar";
$iso3166['454'] = "Malawi";
$iso3166['458'] = "Malaysia";
$iso3166['462'] = "Maldives";
$iso3166['466'] = "Mali";
$iso3166['470'] = "Malta";
$iso3166['584'] = "Marshall Islands";
$iso3166['474'] = "Martinique";
$iso3166['478'] = "Mauritania";
$iso3166['480'] = "Mauritius";
$iso3166['175'] = "Mayotte";
$iso3166['484'] = "Mexico";
$iso3166['583'] = "Micronesia (Federated States of)";
$iso3166['498'] = "Moldova (Republic of)";
$iso3166['492'] = "Monaco";
$iso3166['496'] = "Mongolia";
$iso3166['499'] = "Montenegro";
$iso3166['500'] = "Montserrat";
$iso3166['504'] = "Morocco";
$iso3166['508'] = "Mozambique";
$iso3166['104'] = "Myanmar";
$iso3166['516'] = "Namibia";
$iso3166['520'] = "Nauru";
$iso3166['524'] = "Nepal";
$iso3166['528'] = "Netherlands";
$iso3166['540'] = "New Caledonia";
$iso3166['554'] = "New Zealand";
$iso3166['558'] = "Nicaragua";
$iso3166['562'] = "Niger";
$iso3166['566'] = "Nigeria";
$iso3166['570'] = "Niue";
$iso3166['574'] = "Norfolk Island";
$iso3166['580'] = "Northern Mariana Islands";
$iso3166['578'] = "Norway";
$iso3166['512'] = "Oman";
$iso3166['586'] = "Pakistan";
$iso3166['585'] = "Palau";
$iso3166['275'] = "Palestine, State of";
$iso3166['591'] = "Panama";
$iso3166['598'] = "Papua New Guinea";
$iso3166['600'] = "Paraguay";
$iso3166['604'] = "Peru";
$iso3166['608'] = "Philippines";
$iso3166['612'] = "Pitcairn";
$iso3166['616'] = "Poland";
$iso3166['620'] = "Portugal";
$iso3166['630'] = "Puerto Rico";
$iso3166['634'] = "Qatar";
$iso3166['638'] = "Réunion";
$iso3166['642'] = "Romania";
$iso3166['643'] = "Russian Federation";
$iso3166['646'] = "Rwanda";
$iso3166['652'] = "Saint Barthélemy";
$iso3166['654'] = "Saint Helena, Ascension and Tristan da Cunha";
$iso3166['659'] = "Saint Kitts and Nevis";
$iso3166['662'] = "Saint Lucia";
$iso3166['663'] = "Saint Martin (French part)";
$iso3166['666'] = "Saint Pierre and Miquelon";
$iso3166['670'] = "Saint Vincent and the Grenadines";
$iso3166['882'] = "Samoa";
$iso3166['674'] = "San Marino";
$iso3166['678'] = "Sao Tome and Principe";
$iso3166['682'] = "Saudi Arabia";
$iso3166['686'] = "Senegal";
$iso3166['688'] = "Serbia";
$iso3166['690'] = "Seychelles";
$iso3166['694'] = "Sierra Leone";
$iso3166['702'] = "Singapore";
$iso3166['534'] = "Sint Maarten (Dutch part)";
$iso3166['703'] = "Slovakia";
$iso3166['705'] = "Slovenia";
$iso3166['090'] = "Solomon Islands";
$iso3166['706'] = "Somalia";
$iso3166['710'] = "South Africa";
$iso3166['239'] = "South Georgia and the South Sandwich Islands";
$iso3166['728'] = "South Sudan";
$iso3166['724'] = "Spain";
$iso3166['144'] = "Sri Lanka";
$iso3166['729'] = "Sudan";
$iso3166['740'] = "Suriname";
$iso3166['744'] = "Svalbard and Jan Mayen";
$iso3166['752'] = "Sweden";
$iso3166['756'] = "Switzerland";
$iso3166['760'] = "Syrian Arab Republic";
$iso3166['158'] = "Taiwan (Province of China)";
$iso3166['762'] = "Tajikistan";
$iso3166['834'] = "Tanzania, United Republic of";
$iso3166['764'] = "Thailand";
$iso3166['626'] = "Timor-Leste";
$iso3166['768'] = "Togo";
$iso3166['772'] = "Tokelau";
$iso3166['776'] = "Tonga";
$iso3166['780'] = "Trinidad and Tobago";
$iso3166['788'] = "Tunisia";
$iso3166['792'] = "Turkey";
$iso3166['795'] = "Turkmenistan";
$iso3166['796'] = "Turks and Caicos Islands";
$iso3166['798'] = "Tuvalu";
$iso3166['800'] = "Uganda";
$iso3166['804'] = "Ukraine";
$iso3166['784'] = "United Arab Emirates";
$iso3166['826'] = "United Kingdom of Great Britain and Northern Ireland";
$iso3166['840'] = "United States of America";
$iso3166['581'] = "United States Minor Outlying Islands";
$iso3166['858'] = "Uruguay";
$iso3166['860'] = "Uzbekistan";
$iso3166['548'] = "Vanuatu";
$iso3166['862'] = "Venezuela (Bolivarian Republic of)";
$iso3166['704'] = "Viet Nam";
$iso3166['092'] = "Virgin Islands (British)";
$iso3166['850'] = "Virgin Islands (U.S.)";
$iso3166['876'] = "Wallis and Futuna";
$iso3166['732'] = "Western Sahara";
$iso3166['887'] = "Yemen";
$iso3166['894'] = "Zambia";
$iso3166['716'] = "Zimbabwe";
 
$out = array();
 
$aid = strtoupper($aid);
$aid = trim($aid);
$aid = str_replace(' ','',$aid);
$aid = str_replace(':','',$aid);
 
if ($aid == '') {
$out[] = "INVALID: The AID is empty";
return $out;
}
 
if (!preg_match('@^[0-9A-F]+$@', $aid, $m)) {
$out[] = "INVALID: AID has invalid characters. Only A..F and 0..9 are allowed";
return $out;
}
 
$aid_hf = implode(':',str_split($aid,2));
if (strlen($aid)%2 == 1) $aid_hf .= '_';
 
$out[] = array("$aid", "ISO/IEC 7816 Application Identifier (AID)");
$out[] = array('', "> $aid_hf <");
$out[] = array('', c_literal_hexstr($aid));
 
if ((strlen($aid) == 32) && (substr($aid,-2) == 'FF')) {
// Sometimes you read that a 16-byte AID must not end with FF, because it is reserved by ISO.
// I have only found one official source:
// ISO/IEC 7816-5 : 1994
// Identification cards - Integrated circuit(s) cards with contacts -
// Part 5 : Numbering system and registration procedure for application identifiers
// https://cdn.standards.iteh.ai/samples/19980/8ff6c7ccc9254fe4b7a8a21c0bf59424/ISO-IEC-7816-5-1994.pdf
// Quote from clause 5.2:
// "The PIX has a free coding. If the AID is 16 bytes long,
// then the value 'FF' for the least significant byte is reserved for future use."
// In the revisions of ISO/IEC 7816, parts of ISO 7816-5 (e.g. the AID categories)
// have been moved to ISO 7816-4.
// The "FF" reservation cannot be found in modern versions of 7816-4 or 7816-5.
/*$out[] = array('',"INVALID: A 16-byte AID must not end with FF. (Reserved by ISO/IEC)");*/
$out[] = array('',"Note: A 16-byte AID ending with FF was reserved by ISO/IEC 7816-5:1994");
}
 
if (strlen($aid) > 32) {
$out[] = array('',"INVALID: An AID must not be longer than 16 bytes");
}
 
$category = substr($aid,0,1);
 
// Category 0..9
// RID = ISO/IEC 7812 Issuer Identification Number (IIN 6 or 8 digits)
// AID = RID + 'FF' + PIX
$iso7812_category = array();
$iso7812_category['0'] = 'ISO/TC 68 and other industry assignments';
$iso7812_category['1'] = 'Airlines';
$iso7812_category['2'] = 'Airlines, financial and other future industry assignments';
$iso7812_category['3'] = 'Travel and entertainment';
$iso7812_category['4'] = 'Banking and financial';
$iso7812_category['5'] = 'Banking and financial';
$iso7812_category['6'] = 'Merchandising and banking/financial';
$iso7812_category['7'] = 'Petroleum and other future industry assignments';
$iso7812_category['8'] = 'Healthcare, telecommunications and other future industry assignments';
$iso7812_category['9'] = 'Assignment by national standards bodies';
foreach ($iso7812_category as $check_cat => $check_cat_name) {
if ("$category" == "$check_cat") { // comparison as string is important so that "===" works. "==" does not work because 0=='A' for some reason!
#$out[] = array($category, "AID based on category $category of ISO/IEC 7812 Issuer Identification Number (IIN)");
#$out[] = array('', "($check_cat = $check_cat_name)");
$out[] = array('', "AID based on ISO/IEC 7812 Issuer Identification Number (IIN)");
 
$iin = $aid;
// IIN and PIX must be delimited with FF, but only if a PIX is available.
// When the IIN has an odd number, then an extra 'F' must be added at the end
$pos = strpos($aid,'F');
if ($pos !== false) $iin = substr($iin, 0, $pos);
 
if (!_is_bcd($iin)) {
$out[] = array($iin, "INVALID: Expected BCD encoded IIN, optionally followed by FF and PIX");
return $out;
}
 
$pad = '';
 
$out[] = 'RID-HERE'; // will (must) be replaced below
 
$out[] = array($iin, "ISO/IEC 7812 Issuer Identification Number (IIN)");
if ((strlen($iin) != 6) && (strlen($iin) != 8)) {
$out[] = array('',"Warning: IIN has an unusual length. 6 or 8 digits are expected!");
}
 
$out[] = array($category, "Major industry identifier $category = $check_cat_name");
$pad .= str_repeat(' ', strlen("$category"));
 
if ("$category" === "9") {
$country = substr($iin,1,3);
if ($country == '') {
$out[] = array($pad.'___', 'ISO/IEC 3166-1 Numeric Country code (missing)');
} else {
$country_name = isset($iso3166[$country]) ? $iso3166[$country] : 'Unknown country';
$out[] = array($pad.str_pad($country,3,'_',STR_PAD_RIGHT), "ISO/IEC 3166-1 Numeric Country code : $country ($country_name)");
}
$pad .= ' ';
$asi = substr($iin,4);
$asn = $asi;
} else {
$asi = substr($iin,1);
$asn = $asi;
}
$out[] = array("$pad$asn", 'Assigned number'.($asi=='' ? ' (missing)' : ''));
if ($asi!='') $out[] = array('', c_literal_hexstr($asi));
$pad .= str_repeat(' ',strlen($asn));
 
$padded_iin = $iin;
if (strlen($iin)%2 != 0) {
$odd_padding = substr($aid,strlen($iin),1);
if ($odd_padding != 'F') {
foreach ($out as $n => &$tmp) {
if ($tmp == 'RID-HERE') {
unset($out[$n]);
break;
}
}
$out[] = array("$pad!","INVALID: An IIN with odd length must be padded with F, e.g. 123 => 123F");
return $out;
}
$out[] = array($pad.$odd_padding, 'Padding of IIN with odd length');
$padded_iin .= $odd_padding;
$pad .= ' ';
}
 
$rid = $padded_iin;
foreach ($out as &$tmp) {
if ($tmp == 'RID-HERE') {
$tmp = array("$rid", "Registered Application Provider Identifier (RID)");
break;
}
}
 
if (strlen($aid) == strlen($padded_iin)) {
// There is no PIX
$out[] = "Proprietary application identifier extension (PIX) missing";
} else {
$delimiter = substr($aid,strlen($padded_iin),2);
if ($delimiter != 'FF') {
$out[] = array($pad.substr($aid,strlen($padded_iin)), "INVALID: RID/IIN and PIX must be delimited by FF");
return $out;
}
$out[] = array($pad.$delimiter, 'Delimiter which separates RID/IIN from PIX');
$pad .= str_repeat(' ',strlen($delimiter));
 
$pix = substr($aid,strlen($padded_iin)+strlen('FF'));
if ($pix == '') {
$out[] = "Proprietary application identifier extension (PIX) missing";
$out[] = "Warning: If PIX is available, FF delimites RID/IIN from PIX. Since PIX is empty, consider removing FF."; // not sure if this is an error or not
} else {
$out[] = array($pad.$pix, "Proprietary application identifier extension (PIX)");
$out[] = array('', c_literal_hexstr($pix));
}
}
 
return $out;
}
}
 
// Category 'A' (International Registration)
// RID = 'A' + 9 digits
// AID = RID + PIX
if ("$category" === "A") {
$rid = substr($aid,0,10);
$rid = str_pad($rid,10,'_',STR_PAD_RIGHT);
 
$pix = substr($aid,10);
 
$asi = substr($aid,1,9);
$asn = str_pad($asi,9,'_',STR_PAD_RIGHT);
 
$out[] = array("$rid", "Registered Application Provider Identifier (RID)");
$out[] = array("$category", "Category $category: International registration");
$out[] = array(" $asn", 'Assigned number, BCD recommended'.($asi=='' ? ' (missing)' : ''));
if ($asi!='') $out[] = array('', c_literal_hexstr($asi));
if ($pix == '') {
$out[] = "Proprietary application identifier extension (PIX) missing";
} else {
$out[] = array(str_pad($pix,strlen($aid),' ',STR_PAD_LEFT), "Proprietary application identifier extension (PIX)");
$out[] = array('', c_literal_hexstr($pix));
}
 
return $out;
}
 
// Category 'D' (Local/National Registration)
// RID = 'D' + 3 digits country code (ISO/IEC 3166-1) + 6 digits
// AID = RID + PIX
if ("$category" === "D") {
$rid = substr($aid,0,10);
$rid = str_pad($rid,10,'_',STR_PAD_RIGHT);
 
$pix = substr($aid,10);
 
$country = substr($aid,1,3);
 
$asi = substr($aid,4,6);
$asn = str_pad($asi,6,'_',STR_PAD_RIGHT);
 
$out[] = array("$rid", "Registered Application Provider Identifier (RID)");
$out[] = array("$category", "Category $category: Local/National registration");
if ($country == '') {
$out[] = array(" ___", "ISO/IEC 3166-1 Numeric Country code (missing)");
} else {
$country_name = isset($iso3166[$country]) ? $iso3166[$country] : 'Unknown country';
$out[] = array(" ".str_pad($country,3,'_',STR_PAD_RIGHT), "ISO/IEC 3166-1 Numeric Country code : $country ($country_name)");
}
$out[] = array(" $asn", 'Assigned number, BCD recommended'.($asi=='' ? ' (missing)' : ''));
if ($asi!='') $out[] = array('', c_literal_hexstr($asi));
if ($pix == '') {
$out[] = "Proprietary application identifier extension (PIX) missing";
} else {
$out[] = array(str_pad($pix,strlen($aid),' ',STR_PAD_LEFT), "Proprietary application identifier extension (PIX)");
$out[] = array('', c_literal_hexstr($pix));
}
 
return $out;
}
 
// Category 'E'
// AID = 'E8' + OID + PIX (OID is DER encoding without type and length)
if ("$category" === "E") {
$out[] = array("$category", "Category $category: Standard");
 
$std_schema = substr($aid,1,1);
if ($std_schema == '8') {
$out[] = array(" $std_schema", "Standard identified by OID");
 
// Start: Try to find out if it is E8+Length+OID (inofficial/illegal) or E8+OID (ISO)
$len_usage = _aid_e8_length_usage($aid);
$include_der_length = true; // In case it is ambiguous , let's say it is E8+Length+OID
// Note that these ambiguous are rare and will only happen inside the root OID 0
if ($len_usage === true) $include_der_length = true;
if ($len_usage === false) $include_der_length = false;
if ($include_der_length) {
// Case E8+Length+OID (inofficial/illegal)
$der_length_hex = substr($aid,2,2);
$der_length_dec = hexdec($der_length_hex);
$out[] = array(" $der_length_hex", "DER encoding length (illegal usage not defined by ISO)");
$pure_der = substr($aid,4);
$indent = 4;
$e8_minmax_measure = 'DER';
$e8_min = $der_length_dec;
$e8_max = $der_length_dec;
} else {
// Case E8+OID (defined by ISO, but only for their 1.0 OID)
$pure_der = substr($aid,2);
$indent = 2;
if (substr($aid,2,2) == '28') { // '28' = OID 1.0 (ISO Standard)
// ISO Standards (OID 1.0) will only have 1 or 2 numbers. (Number 1 is the standard, and number 2
// is the part in case of a multi-part standard).
$e8_minmax_measure = 'ARC';
$e8_min = 3; // 1.0.aaaa (ISO AAAA)
$e8_max = 4; // 1.0.aaaa.b (ISO AAAA-B)
} else {
// This is the inofficial usage of E8+OID, i.e. using an OID outside of arc 1.0
$e8_minmax_measure = 'ARC';
$e8_min = 2; // At least 2 arcs (OID x.y)
$e8_max = -1; // no limit
}
}
// End: Try to find out if it is E8+Length+OID (inofficial/illegal) or E8+OID (ISO)
 
try {
$interpretations = _aid_e8_interpretations($pure_der,$e8_minmax_measure,$e8_min,$e8_max);
foreach ($interpretations as $ii => $interpretation) {
$pos = $interpretation[0];
$txt1 = $interpretation[1]; // Standard
$txt2 = $interpretation[2]; // PIX (optional)
 
$aid1 = str_repeat(' ',$indent).substr($pure_der,0,$pos*2);
$aid2 = substr($pure_der,$pos*2);
 
$out[] = array("$aid1", "$txt1");
if ($txt2 !== '') {
$pix = "$txt2 (".c_literal_hexstr(str_replace(':','',$txt2)).")";
$out[] = array(str_repeat(' ',strlen($aid1))."$aid2", "with PIX $pix");
}
if ($ii < count($interpretations)-1) {
$out[] = array('', 'or:');
}
}
} catch (Exception $e) {
$out[] = array(str_repeat(' ',$indent).$pure_der, "ERROR: ".$e->getMessage());
}
} else if ($std_schema != '') {
// E0..E7, E9..EF are RFU
$unknown = substr($aid,1);
$out[] = array(" $unknown", "ILLEGAL USAGE / RESERVED");
}
 
return $out;
}
 
// Category 'F'
// AID = 'F' + PIX
if ("$category" === "F") {
$out[] = array("$category", "Category $category: Non-registered / Proprietary");
$rid = substr($aid,0,1);
$pix = substr($aid,1);
if ($pix == '') {
$out[] = "Proprietary application identifier extension (PIX) missing";
} else {
$out[] = array(' '.$pix, "Proprietary application identifier extension (PIX)");
$out[] = array('', c_literal_hexstr($pix));
}
return $out;
}
 
// Category 'B' and 'C' are reserved
$out[] = array("$category", "Category $category: ILLEGAL USAGE / RESERVED");
if (strlen($aid) > 1) {
$aid_ = substr($aid,1);
$out[] = array(" ".$aid_, "Unknown composition");
$out[] = array('', c_literal_hexstr($aid_));
}
return $out;
}
 
/* --- Small extra function: not part of the decoder --- */
 
function aid_split_rid_pix($a, &$rid=null, &$pix=null) {
// "Quick'n'Dirty" function which does not do any consistency checks!
// It expects that the string is a valid AID!
 
$cat = substr($a,0,1);
if (is_numeric($cat)) {
$p = strpos($a,'F');
if ($p%2 != 0) $p++;
} else if (($cat == 'A') || ($cat == 'D')) {
$p = 10;
} else if ($cat == 'F') {
$p = 1;
} else {
$p = 0;
}
 
if ($rid !== null) $rid = substr($a, 0, $p);
if ($pix !== null) $pix = substr($a, $p);
 
return $p;
}
 
function aid_canonize(&$aid_candidate) {
$aid_candidate = str_replace(' ', '', $aid_candidate);
$aid_candidate = str_replace(':', '', $aid_candidate);
$aid_candidate = strtoupper($aid_candidate);
if (strlen($aid_candidate) > 16*2) {
$aid_is_ok = false; // OID DER encoding is too long to fit into the AID
} else if ((strlen($aid_candidate) == 16*2) && (substr($aid_candidate,-2) == 'FF')) {
$aid_is_ok = false; // 16 byte AID must not end with 0xFF (reserved by ISO)
} else {
$aid_is_ok = true;
}
return $aid_is_ok;
}
/trunk/anti_xss.inc.php
0,0 → 1,85
<?php
 
/*
* Anti XSS
* Copyright 2019 Daniel Marschall, ViaThinkSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
// This function prevents XSS, while still allowing the usage of HTML
function anti_xss($str, $forbidden_tags=array('script','base','meta','link')) {
// Avoid usage of <script tags, but still allow tags that might
// have the prefix forbidden tagname as prefix (useful if you want
// to block other tags other than "script").
// $str = preg_replace('@<(/{0,1}script[^a-zA-Z])@i', '&lt;\\1', $str);
foreach ($forbidden_tags as $tagname) {
if ($tagname == '*') {
$str = str_replace('<', '&lt;', $str);
} else {
$str = preg_replace('@<(/{0,1}'.preg_quote($tagname,'@').'[^a-zA-Z])@i', '&lt;\\1', $str);
}
}
 
if (preg_grep('@^script$@i' , $forbidden_tags)) {
// (1) Avoid stuff like a href="javascript:"
$str = preg_replace('@(javascript|livescript|vbscript)\s*:@im', '\\1<!-- -->:', $str);
 
// (2) Avoid injection of JavaScript events like onload=, but still allow HTML tags that might start with <on...
$str = preg_replace('@O([nN][a-zA-Z]+\s*=)@m', '&#x4F;\\1', $str);
$str = preg_replace('@o([nN][a-zA-Z]+\s*=)@m', '&#x6F;\\1', $str);
}
 
return $str;
}
 
 
 
# Some testcases
#echo anti_xss('hallo welt <script>alert(1)</script>');
#echo anti_xss('<svg onload'."\n\n\r\t".'="alert(1)" src=""></svg><online></online> on august ONLINE');
#echo anti_xss('<svg/onload=alert(\'XSS\')>');
#echo '<a href="'.anti_xss('" onclick="alert(1)').'">Click me</a>';
#echo anti_xss('<a href="">foo</a> <abc>xxx</abc>', array('a'));
#echo anti_xss('<a href="">foo</a> <abc>xxx</abc>', array('*'));
#echo anti_xss("<a href=\"JaVaScRiPt:alert('XSS')\">foobar</a> <pre>JavaScript: is cool</pre>");
#echo anti_xss("<a href=\"JaVaScRiPt : alert('XSS')\">foobar</a> <pre>JavaScript : is cool</pre>");
#echo anti_xss("<a href=\"#\" onclick=\"vbscript:msgbox(\"XSS\")\">foobar</a> <pre>VbScript: is cool</pre>");
#echo anti_xss('<META HTTP-EQUIV="Set-Cookie" Content="USERID=<SCRIPT>alert(\'XSS\')</SCRIPT>">');
 
# Currently we don't support these XSS vectors. But I am unsure if they work at all, in modern browsers
#echo anti_xss('<BR SIZE="&{alert(\'XSS\')}">');
#echo anti_xss('<a href="" onclick="java&#x0d;script:alert(\'1\')">bla</a>');
 
# Currently we are vulnerable to this vectors
# (does not work with Chrome)
#echo anti_xss('<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" AllowScriptAccess="always"></EMBED>');
#echo anti_xss('<IMG STYLE="xss:expr/*XSS*/ession(alert(\'XSS\'))">'); // only IE
 
# TODO: find more vectors from cheat sheets
# https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
 
/*
if (isset($_POST['blabla'])) {
echo anti_xss($_POST['blabla']);
#echo $_POST['blabla'];
} else {
 
echo '<form method="POST" action="anti_xss.php">';
#echo '<textarea name="blabla">'.$_POST['blabla'].'</textarea>';
echo '<textarea name="blabla"></textarea>';
echo '<input type="submit">';
echo '</form>';
}
*/
 
/trunk/color_utils.inc.php
0,0 → 1,182
<?php
 
/*
* Color Utils for PHP
* Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
* Revision 2023-08-29
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
// Some of these functions were taken from other sources.
 
function RGB_TO_HSV($R, $G, $B) { // RGB Values:Number 0-255
// HSV Results:Number 0-1
$HSL = array();
 
$var_R = ($R / 255);
$var_G = ($G / 255);
$var_B = ($B / 255);
 
$var_Min = min($var_R, $var_G, $var_B);
$var_Max = max($var_R, $var_G, $var_B);
$del_Max = $var_Max - $var_Min;
 
$V = $var_Max;
 
if ($del_Max == 0) {
$H = 0;
$S = 0;
} else {
$S = $del_Max / $var_Max;
 
$del_R = ((($var_Max - $var_R) / 6) + ($del_Max / 2)) / $del_Max;
$del_G = ((($var_Max - $var_G) / 6) + ($del_Max / 2)) / $del_Max;
$del_B = ((($var_Max - $var_B) / 6) + ($del_Max / 2)) / $del_Max;
 
if ($var_R == $var_Max) $H = $del_B - $del_G;
else if ($var_G == $var_Max) $H = (1/3) + $del_R - $del_B;
else if ($var_B == $var_Max) $H = (2/3) + $del_G - $del_R;
else $H = 0;
 
if ($H<0) $H++;
if ($H>1) $H--;
}
 
return array($H, $S, $V);
}
 
function HSV_TO_RGB($H, $S, $V) { // HSV Values:Number 0-1
// RGB Results:Number 0-255
$RGB = array();
 
if($S == 0) {
$R = $G = $B = $V * 255;
} else {
$var_H = $H * 6;
$var_i = floor( $var_H );
$var_1 = $V * ( 1 - $S );
$var_2 = $V * ( 1 - $S * ( $var_H - $var_i ));
$var_3 = $V * ( 1 - $S * (1 - ($var_H - $var_i )));
 
if ($var_i == 0) { $var_R = $V ; $var_G = $var_3 ; $var_B = $var_1 ; }
else if ($var_i == 1) { $var_R = $var_2 ; $var_G = $V ; $var_B = $var_1 ; }
else if ($var_i == 2) { $var_R = $var_1 ; $var_G = $V ; $var_B = $var_3 ; }
else if ($var_i == 3) { $var_R = $var_1 ; $var_G = $var_2 ; $var_B = $V ; }
else if ($var_i == 4) { $var_R = $var_3 ; $var_G = $var_1 ; $var_B = $V ; }
else { $var_R = $V ; $var_G = $var_1 ; $var_B = $var_2 ; }
 
$R = $var_R * 255;
$G = $var_G * 255;
$B = $var_B * 255;
}
 
return array($R, $G, $B);
}
 
function rgb2html($r, $g=-1, $b=-1) {
if (is_array($r) && sizeof($r) == 3) {
list($r, $g, $b) = $r;
}
 
$r = intval($r);
$g = intval($g);
$b = intval($b);
 
$r = dechex($r<0 ? 0 : ($r>255 ? 255 : $r));
$g = dechex($g<0 ? 0 : ($g>255 ? 255 : $g));
$b = dechex($b<0 ? 0 : ($b>255 ? 255 : $b));
 
$color = (strlen($r) < 2 ? '0' : '').$r;
$color .= (strlen($g) < 2 ? '0' : '').$g;
$color .= (strlen($b) < 2 ? '0' : '').$b;
return '#'.$color;
}
 
// TODO: Also support hsl() and hsla() color schemes
function changeCSSWithRgbFunction($css_content, $rgb_function) {
 
$i = 0;
do {
$i++;
$dummy = "[$i]";
} while (strpos($css_content, $dummy) !== false);
 
$css_content = preg_replace('@(\\}\\s*)#@ismU', '\\1'.$dummy, $css_content);
$css_content = preg_replace('@(^\\s*)#@isU', '\\1'.$dummy, $css_content);
$css_content = preg_replace_callback('@#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})@ismU',
function ($x) use ($rgb_function) {
if (strlen($x[1]) == 3) {
$r = hexdec($x[1][0].$x[1][0]);
$g = hexdec($x[1][1].$x[1][1]);
$b = hexdec($x[1][2].$x[1][2]);
} else {
$r = hexdec($x[1][0].$x[1][1]);
$g = hexdec($x[1][2].$x[1][3]);
$b = hexdec($x[1][4].$x[1][5]);
}
$rgb_function($r,$g,$b);
return rgb2html($r,$g,$b);
}, $css_content);
$css_content = preg_replace_callback('@rgb\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)@ismU',
function ($x) use ($rgb_function) {
$r = $x[1];
$g = $x[2];
$b = $x[3];
$rgb_function($r,$g,$b);
return "rgb($r,$g,$b)";
}, $css_content);
$css_content = preg_replace_callback('@rgba\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*([\\d\\.]+)\\s*\\)@ismU',
function ($x) use ($rgb_function) {
$r = $x[1];
$g = $x[2];
$b = $x[3];
$a = $x[4];
$rgb_function($r,$g,$b);
return "rgba($r,$g,$b,$a)";
}, $css_content);
$css_content = preg_replace_callback('@\\-rgb:\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*;@ismU',
// Bootstrap uses "--bs-link-color-rgb: 13,110,253;" which we must also accept
function ($x) use ($rgb_function) {
$r = $x[1];
$g = $x[2];
$b = $x[3];
$rgb_function($r,$g,$b);
return "-rgb:$r,$g,$b;";
}, $css_content);
$css_content = str_replace($dummy, '#', $css_content);
return $css_content;
}
 
function changeHueOfCSS($css_content, $h_shift=0, $s_shift=0, $v_shift=0) {
$rgb_function = function(&$r,&$g,&$b) use ($h_shift, $s_shift, $v_shift) {
list ($h,$s,$v) = RGB_TO_HSV($r, $g, $b);
$h = (float)$h;
$s = (float)$s;
$v = (float)$v;
$h = ($h + $h_shift); while ($h > 1) $h -= 1; while ($h < 0) $h += 1;
$s = ($s + $s_shift); while ($s > 1) $s = 1; while ($s < 0) $s = 0;
$v = ($v + $v_shift); while ($v > 1) $v = 1; while ($v < 0) $v = 0;
list ($r,$g,$b) = HSV_TO_RGB($h, $s, $v);
};
return changeCSSWithRgbFunction($css_content, $rgb_function);
}
 
function invertColorsOfCSS($css_content) {
$rgb_function = function(&$r,&$g,&$b) {
$r = 255 - $r;
$g = 255 - $g;
$b = 255 - $b;
};
return changeCSSWithRgbFunction($css_content, $rgb_function);
}
/trunk/composer.json
Cannot display: file marked as a binary type.
svn:mime-type = application/json
Property changes:
Added: svn:mime-type
+application/json
\ No newline at end of property
/trunk/decode_jwt_token.inc.php
0,0 → 1,122
<?php
 
/*
* JWT Decoder for PHP
* Copyright 2021 Daniel Marschall, ViaThinkSoft
* Version 2021-05-15
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
function decode_idtoken($id_token, $verification_certs=null, $allowed_algorithms = array()) {
// Parts taken and simplified from https://github.com/firebase/php-jwt , licensed by BSD-3-clause
// Here is a great page for encode and decode tokens for testing: https://jwt.io/
 
$parts = explode('.', $id_token);
if (count($parts) === 5) return false; // encrypted JWT not yet supported
if (count($parts) !== 3) return false;
list($header_base64, $payload_base64, $signature_base64) = $parts;
 
$header_ary = json_decode(urlsafeB64Decode($header_base64),true);
if ($header_ary['typ'] !== 'JWT') return false;
 
if ($verification_certs) {
$key = isset($header_ary['kid']) ? $verification_certs[$header_ary['kid']] : $verification_certs;
 
$msg = $header_base64.'.'.$payload_base64;
$signature = urlsafeB64Decode($signature_base64);
 
$jwt_algo = $header_ary['alg'];
 
// see https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
// https://datatracker.ietf.org/doc/html/rfc8725#section-3.1
if (!in_array($jwt_algo, $allowed_algorithms)) return false;
 
if ($jwt_algo != 'none') {
$php_algo = 'SHA'.substr($jwt_algo,2,3);
switch (substr($jwt_algo,0,2)) {
case 'ES':
// OpenSSL expects an ASN.1 DER sequence for ES256 signatures
$signature = signatureToDER($signature);
if (!function_exists('openssl_verify')) break; // if OpenSSL is not installed, we just accept the JWT
if (!openssl_verify($msg, $signature, $key, $php_algo)) return false;
break;
case 'RS':
if (!function_exists('openssl_verify')) break; // if OpenSSL is not installed, we just accept the JWT
if (!openssl_verify($msg, $signature, $key, $php_algo)) return false;
break;
case 'HS':
$hash = @hash_hmac($php_algo, $msg, $key, true);
if (!$hash) break; // if the hash algo is not available, we just accept the JWT
if (!hash_equals($hash, $signature)) return false;
break;
case 'PS':
// This feature is new and not yet available in php-jwt
file_put_contents($msg_file = tempnam("/tmp", ""), $msg);
file_put_contents($sig_file = tempnam("/tmp", ""), $signature);
file_put_contents($key_file = tempnam("/tmp", ""), $key);
$ec = -1;
$out = array();
$cmd = "openssl dgst -".strtolower($php_algo)." -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1 -verify ".escapeshellarg($key_file)." -signature ".escapeshellarg($sig_file)." ".escapeshellarg($msg_file);
$cmd .= (strtoupper(substr(PHP_OS,0,3)) === 'WIN') ? ' 2> NUL' : ' 2> /dev/null';
exec($cmd, $out, $ec);
unlink($msg_file);
unlink($sig_file);
unlink($key_file);
if (($ec !== 0) && (count($out) === 0)) break; // If OpenSSL is not found, we just accept the JWT
if (($ec !== 0) || (strpos(implode("\n",$out),"Verified OK") === false)) return false;
break;
default:
return false;
}
}
}
 
$payload_ary = json_decode(urlsafeB64Decode($payload_base64), true);
 
$leeway = 60; // 1 Minute
if (isset($payload_ary['nbf']) && (time()+$leeway<$payload_ary['nbf'])) return false;
if (isset($payload_ary['exp']) && (time()-$leeway>$payload_ary['exp'])) return false;
 
return $payload_ary;
}
 
function urlsafeB64Decode($input) {
// Taken from https://github.com/firebase/php-jwt , licensed by BSD-3-clause
$remainder = strlen($input) % 4;
if ($remainder) {
$padlen = 4 - $remainder;
$input .= str_repeat('=', $padlen);
}
return base64_decode(strtr($input, '-_', '+/'));
}
 
function signatureToDER($sig) {
// Taken from https://github.com/firebase/php-jwt , licensed by BSD-3-clause, modified
 
// Separate the signature into r-value and s-value
list($r, $s) = str_split($sig, (int) (strlen($sig) / 2));
 
// Trim leading zeros
$r = ltrim($r, "\x00");
$s = ltrim($s, "\x00");
 
// Convert r-value and s-value from unsigned big-endian integers to signed two's complement
if (ord($r[0]) > 0x7f) $r = "\x00" . $r;
if (ord($s[0]) > 0x7f) $s = "\x00" . $s;
 
$der_r = chr(0x00/*primitive*/ | 0x02/*INTEGER*/).chr(strlen($r)).$r;
$der_s = chr(0x00/*primitive*/ | 0x02/*INTEGER*/).chr(strlen($s)).$s;
$der = chr(0x20/*constructed*/ | 0x10/*SEQUENCE*/).chr(strlen($der_r.$der_s)).$der_r.$der_s;
return $der;
}
/trunk/fixed_length_microtime.inc.php
0,0 → 1,35
<?php
 
/*
* fixed_length_microtime() for PHP
* Copyright 2022 Daniel Marschall, ViaThinkSoft
* Version 2022-03-08
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
function fixed_length_microtime($unique=true) {
// This function outputs a fixed-length microtime (can be used for sorting)
// Optionally, it ensures that the output is always different by waiting 1 microsecond
 
$ary = explode('.', (string)microtime(true));
if (!isset($ary[1])) $ary[1] = 0;
$ret = $ary[0].'_'.str_pad($ary[1], 4, '0', STR_PAD_RIGHT);
 
if ($unique) {
// Make sure value changes by waiting 1 microsecond.
usleep(1);
}
 
return $ret;
}
/trunk/functions_diff.inc.php
0,0 → 1,82
<?php
 
/*
* PHP diff functions
* Copyright 2012 Daniel Marschall, ViaThinkSoft
* Revision 2012-11-16
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
define('TABS_WS', 6);
 
function output_unified_diff($fileA, $fileB, $num_lines=3) {
$fileA = realpath($fileA);
$fileB = realpath($fileB);
 
ob_start();
system("diff -wbB --ignore-blank-lines -U ".escapeshellarg($num_lines)." ".escapeshellarg($fileA)." ".escapeshellarg($fileB));
$cont = ob_get_contents();
ob_end_clean();
 
$ary = explode("\n", $cont);
foreach ($ary as $n => $a) {
$c = substr($a, 0, 1);
$c2 = substr($a, 0, 2);
$c3 = substr($a, 0, 3);
 
echo '<code>';
if (($c3 == '+++') || ($c3 == '---')) {
echo '<b><font color="gray">'.html_format($a).'</font></b>';
} else if ($c2 == '@@') {
echo '<b><font color="blue">'.html_format($a).'</font></b>';
} else if ($c == '+') {
echo '<font color="green">'.html_format($a).'</font>';
} else if ($c == '-') {
echo '<font color="red">'.html_format($a).'</font>';
} else {
echo html_format($a);
}
echo "</code><br />\n";
}
}
 
function output_diff($fileA, $fileB, $num_lines=3) {
$fileA = realpath($fileA);
$fileB = realpath($fileB);
 
ob_start();
system("diff -wbB --ignore-blank-lines ".escapeshellarg($fileA)." ".escapeshellarg($fileB));
$cont = ob_get_contents();
ob_end_clean();
 
$ary = explode("\n", $cont);
foreach ($ary as $n => $a) {
$c = substr($a, 0, 1);
 
echo '<code>';
if (($c == '>') || ($c == '<')) {
echo '<b><font color="blue">'.html_format($c).'</font></b>'.html_format(substr($a, 1));
} else {
echo '<b><font color="blue">'.html_format($a).'</font></b>';
}
echo "</code><br />\n";
}
}
 
function html_format($x) {
$x = htmlentities($x);
$x = str_replace("\t", str_repeat(' ', TABS_WS), $x);
$x = str_replace(' ', '&nbsp;', $x);
return $x;
}
/trunk/git_utils.inc.php
0,0 → 1,302
<?php
 
/*
* PHP git functions
* Copyright 2021 - 2023 Daniel Marschall, ViaThinkSoft
* Revision 2023-04-10
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
function git_get_latest_commit_id(string $git_dir): string {
// Note: The method "getTip()" of GLIP only implements "refs/heads/master" and "packed-refs" (but for packed-refs without "refs/remotes/origin/...")
 
$cont = @file_get_contents($git_dir . '/HEAD');
if (preg_match('@ref: (.+)[\r\n]@', "$cont\n", $m) && file_exists($git_dir . '/' . $m[1])) {
// Example content of a .git folder file:
// 091a5fa6b157be035e88f5d24aa329ba44d20d63
return trim(file_get_contents($git_dir . '/' . $m[1]));
}
 
if (file_exists($git_dir . '/refs/heads/master')) {
// Missing at Plesk Git initial checkout, but available on update.
return trim(file_get_contents($git_dir . '/refs/heads/master'));
}
 
if (file_exists($git_dir . '/packed-refs')) {
// Example contents of the file:
// # pack-refs with: peeled fully-peeled sorted
// 5605bd539677494558470234266cb5885343e72b refs/remotes/origin/master
// a3d910dd0cdca30827ae25b0f89045d8403b8843 refs/remotes/origin/patch-1
$subpaths = ['refs/heads/master', 'refs/remotes/origin/master'];
foreach ($subpaths as $subpath) {
$head = null;
$f = fopen($git_dir . '/packed-refs', 'rb');
flock($f, LOCK_SH);
while ($head === null && ($line = fgets($f)) !== false) {
if ($line[0] == '#')
continue;
$parts = explode(' ', trim($line));
if (count($parts) == 2 && $parts[1] == $subpath)
$head = $parts[0];
}
fclose($f);
if ($head !== null)
return $head;
}
}
 
if (file_exists($git_dir . '/FETCH_HEAD')) {
// Example content of a Plesk Git folder (fresh):
// 091a5fa6b157be035e88f5d24aa329ba44d20d63 not-for-merge branch 'master' of https://github.com/danielmarschall/oidplus
// 091a5fa6b157be035e88f5d24aa329ba44d20d63 not-for-merge remote-tracking branch 'origin/trunk' of https://github.com/danielmarschall/oidplus
$cont = file_get_contents($git_dir . '/FETCH_HEAD');
return substr(trim($cont), 0, 40);
}
 
throw new Exception("Cannot detect latest Commit ID");
}
 
function git_get_latest_commit_message(string $git_dir): string {
// First try an official git client
$cmd = "git --git-dir=" . escapeshellarg("$git_dir") . " log -1 2>&1";
$ec = -1;
$out = array();
@exec($cmd, $out, $ec);
$out = implode("\n", $out);
if (($ec == 0) && ($out != '')) return $out;
 
// If that failed, try to decode the binary files ourselves
$commit_object = git_get_latest_commit_id($git_dir);
$objects_dir = $git_dir . '/objects';
 
// Sometimes, objects are uncompressed, sometimes compressed in a pack file
// Plesk initial checkout is compressed, but pulls via web interface
// save uncompressed files
 
if (class_exists('ViaThinkSoft\Glip\Git')) {
// https://github.com/danielmarschall/glip
// composer require danielmarschall/glip
$git = new ViaThinkSoft\Glip\Git($git_dir);
$obj = $git->getObject(hex2bin($commit_object));
return $obj->detail;
} else {
// Own implementation (the compressed read cannot handle delta objects yet)
 
$uncompressed_file = $objects_dir . '/' . substr($commit_object, 0, 2) . '/' . substr($commit_object, 2);
if (file_exists($uncompressed_file)) {
// Read compressed data
$compressed = file_get_contents($uncompressed_file);
 
// Uncompress
$uncompressed = @gzuncompress($compressed);
if ($uncompressed === false) throw new Exception("Decompression failed");
 
// The format is "<type> <size>\0<Message>"
list($hdr, $object_data) = explode("\0", $uncompressed, 2);
// sscanf($hdr, "%s %d", $type, $object_size);
return $object_data;
} else {
$pack_files = @glob($objects_dir . '/pack/pack-*.pack');
if ($pack_files) {
foreach ($pack_files as $basename) {
$basename = substr(basename($basename), 0, strlen(basename($basename)) - 5);
return git_read_object($commit_object,
$objects_dir . '/pack/' . $basename . '.idx',
$objects_dir . '/pack/' . $basename . '.pack'
);
}
}
throw new Exception("No pack files found");
}
}
}
 
function git_read_object(string $object_wanted, string $idx_file, string $pack_file, bool $debug = false): string {
// More info about the IDX and PACK format: https://git-scm.com/docs/pack-format
 
// Do some checks
if (!preg_match('/^[0-9a-fA-F]{40}$/', $object_wanted, $m)) throw new Exception("Is not a valid object: $object_wanted");
if (!file_exists($idx_file)) throw new Exception("Idx file $idx_file not found");
if (!file_exists($pack_file)) throw new Exception("Pack file $pack_file not found");
 
// Open index file
$fp = fopen($idx_file, 'rb');
if (!$fp) throw new Exception("Cannot open index file $idx_file");
 
// Read version
fseek($fp, 0);
$unpacked = unpack('H8', fread($fp, 4)); // H8 = 8x "Hex string, high nibble first"
if ($unpacked[1] === bin2hex("\377tOc")) {
$version = unpack('N', fread($fp, 4))[1]; // N = "unsigned long (always 32 bit, big endian byte order)"
$fanout_offset = 8;
if ($version != 2) throw new Exception("Version $version unknown");
} else {
$version = 1;
$fanout_offset = 0;
}
if ($debug) echo "Index file version = $version\n";
 
// Read fanout table
fseek($fp, $fanout_offset);
$fanout_ary[0] = 0;
$fanout_ary = unpack('N*', fread($fp, 4 * 256));
$num_objects = $fanout_ary[256];
 
// Find out approximate object number (from fanout table)
$fanout_index = hexdec(substr($object_wanted, 0, 2));
if ($debug) echo "Fanout index = " . ($fanout_index - 1) . "\n";
$object_no = $fanout_ary[$fanout_index]; // approximate
if ($debug) echo "Object no approx $object_no\n";
 
// Find the exact object number
fseek($fp, $fanout_offset + 4 * 256 + 20 * $object_no);
$object_no--;
$pack_offset = -1; // avoid that phpstan complains
do {
$object_no++;
if ($version == 1) {
$pack_offset = fread($fp, 4);
}
$binary = fread($fp, 20);
if (substr(bin2hex($binary), 0, 2) != substr(strtolower($object_wanted), 0, 2)) {
throw new Exception("Object $object_wanted not found");
}
} while (bin2hex($binary) != strtolower($object_wanted));
if ($debug) echo "Exact object no = $object_no\n";
 
if ($version == 2) {
// Get CRC32
fseek($fp, $fanout_offset + 4 * 256 + 20 * $num_objects + 4 * $object_no);
$crc32 = unpack('H8', fread($fp, 4))[1];
if ($debug) echo "CRC32 = " . $crc32 . "\n";
 
// Get offset (32 bit)
fseek($fp, $fanout_offset + 4 * 256 + 20 * $num_objects + 4 * $num_objects + 4 * $object_no);
$offset_info = unpack('N', fread($fp, 4))[1];
if ($offset_info >= 0x80000000) {
// MSB set, so the offset is 64 bit
if ($debug) echo "64 bit pack offset\n";
$offset_info &= 0x7FFFFFFF;
fseek($fp, $fanout_offset + 4 * 256 + 20 * $num_objects + 4 * $num_objects + 4 * $num_objects + 8 * $offset_info);
$pack_offset = unpack('J', fread($fp, 8))[1];
} else {
// MSB is not set, so the offset is 32 bit
if ($debug) echo "32 bit pack offset\n";
$offset_info &= 0x7FFFFFFF;
$pack_offset = $offset_info;
}
}
 
if ($debug) echo "Pack file offset = " . sprintf('0x%x', $pack_offset) . "\n";
 
// Close index file
fclose($fp);
 
// Open pack file
$fp = fopen($pack_file, 'rb');
if (!$fp) throw new Exception("Cannot open pack file $pack_file");
 
// Read type and first part of the size
fseek($fp, $pack_offset);
$size_info = unpack('C', fread($fp, 1))[1];
 
// Detect type
$type = ($size_info & 0x70) >> 4; /*0b01110000*/
switch ($type) {
case 1:
if ($debug) echo "Type = OBJ_COMMIT ($type)\n";
break;
case 2:
if ($debug) echo "Type = OBJ_TREE ($type)\n";
break;
case 3:
if ($debug) echo "Type = OBJ_BLOB ($type)\n";
break;
case 4:
if ($debug) echo "Type = OBJ_TAG ($type)\n";
break;
case 6:
if ($debug) echo "Type = OBJ_OFS_DELTA ($type)\n";
break;
case 7:
if ($debug) echo "Type = OBJ_REF_DELTA ($type)\n";
break;
default:
if ($debug) echo "Type = Invalid ($type)\n";
break;
}
 
// Find out the expected unpacked size
$size = $size_info & 0xF /*0x00001111*/
;
$shift_info = 4;
while ($size_info >= 0x80) {
$size_info = unpack('C', fread($fp, 1))[1];
$size = (($size_info & 0x7F) << $shift_info) + $size;
$shift_info += 7;
}
if ($debug) echo "Expected unpacked size = $size\n";
 
// Read delta base type
// Example implementation: https://github.com/AlexFBP/glip/blob/master/lib/git.class.php#L240
if ($type == 6/*OBJ_OFS_DELTA*/) {
// "a negative relative offset from the delta object's position in the pack if this is an OBJ_OFS_DELTA object"
 
// Offset encoding
$offset = 0;
$shift_info = 0;
do {
$offset_info = unpack('C', fread($fp, 1))[1];
$offset = (($offset_info & 0x7F) << $shift_info) + $offset;
$shift_info += 7;
} while ($offset_info >= 0x80);
 
if ($debug) echo "Delta negative offset: $offset\n";
throw new Exception("OBJ_OFS_DELTA is currently not implemented"); // TODO! Implement OBJ_OFS_DELTA!
}
if ($type == 7/*OBJ_REF_DELTA*/) {
// "base object name if OBJ_REF_DELTA"
$delta_info = bin2hex(fread($fp, 20));
if ($debug) echo "Delta base object name: $delta_info\n";
throw new Exception("OBJ_REF_DELTA is currently not implemented"); // TODO! Implement OBJ_REF_DELTA!
}
 
// Read and uncompress the compressed data
$compressed = '';
$uncompressed = false;
for ($compressed_size = 1; $compressed_size <= 32768 * $size; $compressed_size++) {
// Since we don't know the compressed size, we need to do trial and error
// TODO: this is a super stupid algorithm! Is there a better way???
$compressed .= fread($fp, 1);
$uncompressed = @gzuncompress($compressed);
if (strlen($uncompressed) === $size) {
if ($debug) echo "Detected compressed size = $compressed_size\n";
break;
}
}
if ($uncompressed === false) throw new Exception("Decompression failed");
if ($debug) echo "$uncompressed\n";
 
// Close pack file
fclose($fp);
 
if ($version == 2) {
// Check CRC32
// TODO: Hash does not match. What are we doing wrong?!
// if ($debug) echo "CRC32 found = ".hash('crc32',$compressed)." vs $crc32\n";
// if ($debug) echo "CRC32 found = ".hash('crc32b',$compressed)." vs $crc32\n";
}
 
return $uncompressed;
}
/trunk/gmp_supplement.inc.php
0,0 → 1,831
<?php
 
/*
* PHP GMP-Supplement implemented using BCMath
* Copyright 2020-2022 Daniel Marschall, ViaThinkSoft
* Version 2021-06-29
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
if (function_exists('bcadd')) {
// ----------------- Implementation of GMP functions using BCMath -----------------
 
if (!function_exists('gmp_init') ) {
define('GMP_ROUND_ZERO', 0);
define('GMP_ROUND_PLUSINF', 1);
define('GMP_ROUND_MINUSINF', 2);
define('GMP_MSW_FIRST', 1);
define('GMP_LSW_FIRST', 2);
define('GMP_LITTLE_ENDIAN', 4);
define('GMP_BIG_ENDIAN', 8);
define('GMP_NATIVE_ENDIAN', 16);
define('GMP_VERSION', '6.0.0');
 
// gmp_abs ( GMP $a ) : GMP
// Absolute value
function gmp_abs($a) {
bcscale(0);
if (bccomp($a, "0") == 1) {
return $a;
} else {
return bcmul($a, "-1");
}
}
 
// gmp_add ( GMP $a , GMP $b ) : GMP
// Add numbers
function gmp_add($a, $b) {
bcscale(0);
 
// bcadd ( string $left_operand , string $right_operand [, int $scale = 0 ] ) : string
return bcadd($a, $b);
}
 
// gmp_and ( GMP $a , GMP $b ) : GMP
// Bitwise AND
function gmp_and($a, $b) {
bcscale(0);
// Convert $a and $b to a binary string
$ab = bc_dec2bin($a);
$bb = bc_dec2bin($b);
$length = max(strlen($ab), strlen($bb));
$ab = str_pad($ab, $length, "0", STR_PAD_LEFT);
$bb = str_pad($bb, $length, "0", STR_PAD_LEFT);
 
// Do the bitwise binary operation
$cb = '';
for ($i=0; $i<$length; $i++) {
$cb .= (($ab[$i] == 1) and ($bb[$i] == 1)) ? '1' : '0';
}
 
// Convert back to a decimal number
return bc_bin2dec($cb);
}
 
// gmp_binomial ( mixed $n , int $k ) : GMP
// Calculates binomial coefficient
function gmp_binomial($n, $k) {
bcscale(0);
throw new Exception("gmp_binomial() NOT IMPLEMENTED");
}
 
// gmp_clrbit ( GMP $a , int $index ) : void
// Clear bit
function gmp_clrbit(&$a, $index) {
bcscale(0);
gmp_setbit($a, $index, false);
}
 
// gmp_cmp ( GMP $a , GMP $b ) : int
// Compare numbers
function gmp_cmp($a, $b) {
bcscale(0);
 
// bccomp ( string $left_operand , string $right_operand [, int $scale = 0 ] ) : int
return bccomp($a, $b);
}
 
// gmp_com ( GMP $a ) : GMP
// Calculates one's complement
function gmp_com($a) {
bcscale(0);
// Convert $a and $b to a binary string
$ab = bc_dec2bin($a);
 
// Swap every bit
for ($i=0; $i<strlen($ab); $i++) {
$ab[$i] = ($ab[$i] == '1' ? '0' : '1');
}
 
// Convert back to a decimal number
return bc_bin2dec($ab);
}
 
// gmp_div_q ( GMP $a , GMP $b [, int $round = GMP_ROUND_ZERO ] ) : GMP
// Divide numbers
function gmp_div_q($a, $b, $round = GMP_ROUND_ZERO/*$round not implemented*/) {
bcscale(0);
 
// bcdiv ( string $dividend , string $divisor [, int $scale = 0 ] ) : string
return bcdiv($a, $b);
}
 
// Divide numbers and get quotient and remainder
// gmp_div_qr ( GMP $n , GMP $d [, int $round = GMP_ROUND_ZERO ] ) : array
function gmp_div_qr($n, $d, $round = GMP_ROUND_ZERO/*$round not implemented*/) {
bcscale(0);
return array(
gmp_div_q($n, $d, $round),
gmp_div_r($n, $d, $round)
);
}
 
// Remainder of the division of numbers
// gmp_div_r ( GMP $n , GMP $d [, int $round = GMP_ROUND_ZERO ] ) : GMP
function gmp_div_r($n, $d, $round = GMP_ROUND_ZERO/*$round not implemented*/) {
bcscale(0);
// The remainder operator can be used with negative integers. The rule is:
// - Perform the operation as if both operands were positive.
// - If the left operand is negative, then make the result negative.
// - If the left operand is positive, then make the result positive.
// - Ignore the sign of the right operand in all cases.
$r = bcmod($n, $d);
if (bccomp($n, "0") < 0) $r = bcmul($r, "-1");
return $r;
}
 
// gmp_div ( GMP $a , GMP $b [, int $round = GMP_ROUND_ZERO ] ) : GMP
// Divide numbers
function gmp_div($a, $b, $round = GMP_ROUND_ZERO/*$round not implemented*/) {
bcscale(0);
return gmp_div_q($a, $b, $round); // Alias von gmp_div_q
}
 
// gmp_divexact ( GMP $n , GMP $d ) : GMP
// Exact division of numbers
function gmp_divexact($n, $d) {
bcscale(0);
return bcdiv($n, $d);
}
 
// gmp_export ( GMP $gmpnumber [, int $word_size = 1 [, int $options = GMP_MSW_FIRST | GMP_NATIVE_ENDIAN ]] ) : string
// Export to a binary string
function gmp_export($gmpnumber, $word_size = 1, $options = GMP_MSW_FIRST | GMP_NATIVE_ENDIAN) {
if ($word_size != 1) throw new Exception("Word size != 1 not implemented");
if ($options != GMP_MSW_FIRST | GMP_NATIVE_ENDIAN) throw new Exception("Different options not implemented");
 
bcscale(0);
$gmpnumber = bcmul($gmpnumber,"1"); // normalize
return gmp_init(bin2hex($gmpnumber), 16);
}
 
// gmp_fact ( mixed $a ) : GMP
// Factorial
function gmp_fact($a) {
bcscale(0);
return bcfact($a);
}
 
// gmp_gcd ( GMP $a , GMP $b ) : GMP
// Calculate GCD
function gmp_gcd($a, $b) {
bcscale(0);
return gmp_gcdext($a, $b)['g'];
}
 
// gmp_gcdext ( GMP $a , GMP $b ) : array
// Calculate GCD and multipliers
function gmp_gcdext($a, $b) {
bcscale(0);
 
// Source: https://github.com/phpseclib/phpseclib/blob/master/phpseclib/Math/BigInteger/Engines/BCMath.php#L285
// modified to make it fit here and to be compatible with gmp_gcdext
 
$s = '1';
$t = '0';
$s_ = '0';
$t_ = '1';
 
while (bccomp($b, '0', 0) != 0) {
$q = bcdiv($a, $b, 0);
 
$temp = $a;
$a = $b;
$b = bcsub($temp, bcmul($b, $q, 0), 0);
 
$temp = $s;
$s = $s_;
$s_ = bcsub($temp, bcmul($s, $q, 0), 0);
 
$temp = $t;
$t = $t_;
$t_ = bcsub($temp, bcmul($t, $q, 0), 0);
}
 
return [
'g' => /*$this->normalize*/($a),
's' => /*$this->normalize*/($s),
't' => /*$this->normalize*/($t)
];
}
 
// gmp_hamdist ( GMP $a , GMP $b ) : int
// Hamming distance
function gmp_hamdist($a, $b) {
bcscale(0);
throw new Exception("gmp_hamdist() NOT IMPLEMENTED");
}
 
// gmp_import ( string $data [, int $word_size = 1 [, int $options = GMP_MSW_FIRST | GMP_NATIVE_ENDIAN ]] ) : GMP
// Import from a binary string
function gmp_import($data, $word_size=1, $options=GMP_MSW_FIRST | GMP_NATIVE_ENDIAN) {
bcscale(0);
 
if ($word_size != 1) throw new Exception("Word size != 1 not implemented");
if ($options != GMP_MSW_FIRST | GMP_NATIVE_ENDIAN) throw new Exception("Different options not implemented");
 
return gmp_init(hex2bin(gmp_strval(gmp_init($data), 16)));
}
 
// gmp_init ( mixed $number [, int $base = 0 ] ) : GMP
// Create GMP number
function gmp_init($number, $base=0) {
bcscale(0);
if ($base == 0) {
// If base is 0 (default value), the actual base is determined from the leading characters:
// if the first two characters are 0x or 0X, hexadecimal is assumed,
// otherwise if the first character is "0", octal is assumed,
// otherwise decimal is assumed.
if (strtoupper(substr($number, 0, 2)) == '0X') {
$base = 16;
} else if (strtoupper(substr($number, 0, 1)) == '0') {
$base = 8;
} else {
$base = 10;
}
}
 
if ($base == 10) {
return $number;
} else {
return base_convert_bigint($number, $base, 10);
}
}
 
// gmp_intval ( GMP $gmpnumber ) : int
// Convert GMP number to integer
function gmp_intval($gmpnumber) {
bcscale(0);
return (int)$gmpnumber;
}
 
// gmp_invert ( GMP $a , GMP $b ) : GMP
// Inverse by modulo
function gmp_invert($a, $b) {
bcscale(0);
 
// Source: https://github.com/CityOfZion/neo-php/blob/master/src/Crypto/NumberTheory.php#L246
 
while (bccomp($a, '0')==-1) {
$a=bcadd($b, $a);
}
while (bccomp($b, $a)==-1) {
$a=bcmod($a, $b);
}
$c=$a;
$d=$b;
$uc=1;
$vc=0;
$ud=0;
$vd=1;
while (bccomp($c, '0')!=0) {
$temp1=$c;
$q=bcdiv($d, $c, 0);
$c=bcmod($d, $c);
$d=$temp1;
$temp2=$uc;
$temp3=$vc;
$uc=bcsub($ud, bcmul($q, $uc));
$vc=bcsub($vd, bcmul($q, $vc));
$ud=$temp2;
$vd=$temp3;
}
$result='';
if (bccomp($d, '1')==0) {
if (bccomp($ud, '0')==1) {
$result=$ud;
} else {
$result=bcadd($ud, $b);
}
} else {
throw new ErrorException("ERROR: $a and $b are NOT relatively prime.");
}
return $result;
}
 
// gmp_jacobi ( GMP $a , GMP $p ) : int
// Jacobi symbol
function gmp_jacobi($a, $p) {
bcscale(0);
 
// Source: https://github.com/CityOfZion/neo-php/blob/master/src/Crypto/NumberTheory.php#L136
 
if ($p>=3 && $p%2==1) {
$a = bcmod($a, $p);
if ($a == '0') return '0';
if ($a == '1') return '1';
$a1 = $a;
$e = 0;
while (bcmod($a1, '2') == '0') {
$a1 = bcdiv($a1, '2');
$e = bcadd($e, '1');
}
$s = (bcmod($e, '2')=='0' || bcmod($p, '8')=='1' || bcmod($p, '8')=='7') ? '1' : '-1';
if ($a1 == '1') return $s;
if (bcmod($p, '4')=='3' && bcmod($a1, '4')=='3') $s = -$s;
return bcmul($s, (string)gmp_jacobi(bcmod($p, $a1), $a1));
} else {
return false;
}
}
 
// gmp_kronecker ( mixed $a , mixed $b ) : int
// Kronecker symbol
function gmp_kronecker($a, $b) {
bcscale(0);
throw new Exception("gmp_kronecker() NOT IMPLEMENTED");
}
 
// gmp_lcm ( mixed $a , mixed $b ) : GMP
// Calculate LCM
function gmp_lcm($a, $b) {
bcscale(0);
 
if ((bccomp($a,'0')==0) && (bccomp($b,'0')==0)) {
return '0';
} else {
return gmp_div(gmp_abs(gmp_mul($a,$b)), gmp_gcd($a,$b));
}
}
 
// gmp_legendre ( GMP $a , GMP $p ) : int
// Legendre symbol
function gmp_legendre($a, $p) {
bcscale(0);
throw new Exception("gmp_legendre() NOT IMPLEMENTED");
}
 
// gmp_mod ( GMP $n , GMP $d ) : GMP
// Modulo operation
function gmp_mod($n, $d) {
bcscale(0);
 
// bcmod ( string $dividend , string $divisor [, int $scale = 0 ] ) : string
return bcmod($n, $d);
}
 
// gmp_mul ( GMP $a , GMP $b ) : GMP
// Multiply numbers
function gmp_mul($a, $b) {
bcscale(0);
 
// bcmul ( string $left_operand , string $right_operand [, int $scale = 0 ] ) : string
return bcmul($a, $b);
}
 
// gmp_neg ( GMP $a ) : GMP
// Negate number
function gmp_neg($a) {
bcscale(0);
return bcmul($a, "-1");
}
 
// gmp_nextprime ( int $a ) : GMP
// Find next prime number
function gmp_nextprime($a) {
bcscale(0);
 
// Source: https://github.com/CityOfZion/neo-php/blob/master/src/Crypto/NumberTheory.php#L692
 
if (bccomp($a, '2') == '-1') {
return '2';
}
$result = gmp_or(bcadd($a, '1'), '1');
while (!gmp_prob_prime($result)) {
$result = bcadd($result, '2');
}
return $result;
}
 
// gmp_or ( GMP $a , GMP $b ) : GMP
// Bitwise OR
function gmp_or($a, $b) {
bcscale(0);
// Convert $a and $b to a binary string
$ab = bc_dec2bin($a);
$bb = bc_dec2bin($b);
$length = max(strlen($ab), strlen($bb));
$ab = str_pad($ab, $length, "0", STR_PAD_LEFT);
$bb = str_pad($bb, $length, "0", STR_PAD_LEFT);
 
// Do the bitwise binary operation
$cb = '';
for ($i=0; $i<$length; $i++) {
$cb .= (($ab[$i] == 1) or ($bb[$i] == 1)) ? '1' : '0';
}
 
// Convert back to a decimal number
return bc_bin2dec($cb);
}
 
// gmp_perfect_power ( mixed $a ) : bool
// Perfect power check
function gmp_perfect_power($a) {
bcscale(0);
throw new Exception("gmp_perfect_power() NOT IMPLEMENTED");
}
 
// gmp_perfect_square ( GMP $a ) : bool
// Perfect square check
function gmp_perfect_square($a) {
bcscale(0);
throw new Exception("gmp_perfect_square() NOT IMPLEMENTED");
}
 
// gmp_popcount ( GMP $a ) : int
// Population count
function gmp_popcount($a) {
bcscale(0);
$ab = bc_dec2bin($a);
return substr_count($ab, '1');
}
 
// gmp_pow ( GMP $base , int $exp ) : GMP
// Raise number into power
function gmp_pow($base, $exp) {
bcscale(0);
 
// bcpow ( string $base , string $exponent [, int $scale = 0 ] ) : string
return bcpow($base, $exp);
}
 
// gmp_powm ( GMP $base , GMP $exp , GMP $mod ) : GMP
// Raise number into power with modulo
function gmp_powm($base, $exp, $mod) {
bcscale(0);
 
// bcpowmod ( string $base , string $exponent , string $modulus [, int $scale = 0 ] ) : string
return bcpowmod($base, $exp, $mod);
}
 
// gmp_prob_prime ( GMP $a [, int $reps = 10 ] ) : int
// Check if number is "probably prime"
function gmp_prob_prime($a, $reps=10) {
bcscale(0);
 
// Source: https://github.com/CityOfZion/neo-php/blob/master/src/Crypto/NumberTheory.php#L655
 
$t = 40;
$k = 0;
$m = bcsub($reps, '1');
while (bcmod($m, '2') == '0') {
$k = bcadd($k, '1');
$m = bcdiv($m, '2');
}
for ($i=0; $i<$t; $i++) {
$a = bcrand('1', bcsub($reps, '1'));
if ($m < 0) {
return new ErrorException("Negative exponents ($m) not allowed");
} else {
$b0 = bcpowmod($a, $m, $reps);
}
if ($b0!=1 && $b0!=bcsub($reps, '1')) {
$j = 1;
while ($j<=$k-1 && $b0!=bcsub($reps, '1')) {
$b0 = bcpowmod($b0, '2', $reps);
if ($b0 == 1) {
return false;
}
$j++;
}
if ($b0 != bcsub($reps, '1')) {
return false;
}
}
}
return true;
}
 
// gmp_random_bits ( int $bits ) : GMP
// Random number
function gmp_random_bits($bits) {
bcscale(0);
$min = 0;
$max = bcsub(bcpow('2', $bits), '1');
return bcrand($min, $max);
}
 
// gmp_random_range ( GMP $min , GMP $max ) : GMP
// Random number
function gmp_random_range($min, $max) {
bcscale(0);
return bcrand($min, $max);
}
 
// gmp_random_seed ( mixed $seed ) : void
// Sets the RNG seed
function gmp_random_seed($seed) {
bcscale(0);
bcrand_seed($seed);
}
 
// gmp_random ([ int $limiter = 20 ] ) : GMP
// Random number (deprecated)
function gmp_random($limiter=20) {
bcscale(0);
throw new Exception("gmp_random() is deprecated! Please use gmp_random_bits() or gmp_random_range() instead.");
}
 
// gmp_root ( GMP $a , int $nth ) : GMP
// Take the integer part of nth root
function gmp_root($a, $nth) {
bcscale(0);
throw new Exception("gmp_root() NOT IMPLEMENTED");
}
 
// gmp_rootrem ( GMP $a , int $nth ) : array
// Take the integer part and remainder of nth root
function gmp_rootrem($a, $nth) {
bcscale(0);
throw new Exception("gmp_rootrem() NOT IMPLEMENTED");
}
 
// gmp_scan0 ( GMP $a , int $start ) : int
// Scan for 0
function gmp_scan0($a, $start) {
bcscale(0);
 
$ab = bc_dec2bin($a);
 
if ($start < 0) throw new Exception("Starting index must be greater than or equal to zero");
if ($start >= strlen($ab)) return $start;
 
for ($i=$start; $i<strlen($ab); $i++) {
if ($ab[strlen($ab)-1-$i] == '0') {
return $i;
}
}
 
return false;
}
 
// gmp_scan1 ( GMP $a , int $start ) : int
// Scan for 1
function gmp_scan1($a, $start) {
bcscale(0);
 
$ab = bc_dec2bin($a);
 
if ($start < 0) throw new Exception("Starting index must be greater than or equal to zero");
if ($start >= strlen($ab)) return -1;
 
for ($i=$start; $i<strlen($ab); $i++) {
if ($ab[strlen($ab)-1-$i] == '1') {
return $i;
}
}
 
return false;
}
 
// gmp_setbit ( GMP $a , int $index [, bool $bit_on = TRUE ] ) : void
// Set bit
function gmp_setbit(&$a, $index, $bit_on=TRUE) {
bcscale(0);
$ab = bc_dec2bin($a);
 
if ($index < 0) throw new Exception("Invalid index");
if ($index >= strlen($ab)) {
$ab = str_pad($ab, $index+1, '0', STR_PAD_LEFT);
}
 
$ab[strlen($ab)-1-$index] = $bit_on ? '1' : '0';
 
$a = bc_bin2dec($ab);
}
 
// gmp_sign ( GMP $a ) : int
// Sign of number
function gmp_sign($a) {
bcscale(0);
return bccomp($a, "0");
}
 
// gmp_sqrt ( GMP $a ) : GMP
// Calculate square root
function gmp_sqrt($a) {
bcscale(0);
 
// bcsqrt ( string $operand [, int $scale = 0 ] ) : string
return bcsqrt($a);
}
 
// gmp_sqrtrem ( GMP $a ) : array
// Square root with remainder
function gmp_sqrtrem($a) {
bcscale(0);
throw new Exception("gmp_sqrtrem() NOT IMPLEMENTED");
}
 
// gmp_strval ( GMP $gmpnumber [, int $base = 10 ] ) : string
// Convert GMP number to string
function gmp_strval($gmpnumber, $base=10) {
bcscale(0);
if ($base == 10) {
return $gmpnumber;
} else {
return base_convert_bigint($gmpnumber, 10, $base);
}
}
 
// gmp_sub ( GMP $a , GMP $b ) : GMP
// Subtract numbers
function gmp_sub($a, $b) {
bcscale(0);
 
// bcsub ( string $left_operand , string $right_operand [, int $scale = 0 ] ) : string
return bcsub($a, $b);
}
 
// gmp_testbit ( GMP $a , int $index ) : bool
// Tests if a bit is set
function gmp_testbit($a, $index) {
bcscale(0);
$ab = bc_dec2bin($a);
 
if ($index < 0) throw new Exception("Invalid index");
if ($index >= strlen($ab)) return ('0' == '1');
 
return $ab[strlen($ab)-1-$index] == '1';
}
 
// gmp_xor ( GMP $a , GMP $b ) : GMP
// Bitwise XOR
function gmp_xor($a, $b) {
bcscale(0);
// Convert $a and $b to a binary string
$ab = bc_dec2bin($a);
$bb = bc_dec2bin($b);
$length = max(strlen($ab), strlen($bb));
$ab = str_pad($ab, $length, "0", STR_PAD_LEFT);
$bb = str_pad($bb, $length, "0", STR_PAD_LEFT);
 
// Do the bitwise binary operation
$cb = '';
for ($i=0; $i<$length; $i++) {
$cb .= (($ab[$i] == 1) xor ($bb[$i] == 1)) ? '1' : '0';
}
 
// Convert back to a decimal number
return bc_bin2dec($cb);
}
}
 
// ----------------- Helper functions -----------------
 
function base_convert_bigint($numstring, $frombase, $tobase) {
$numstring = "".$numstring;
 
$frombase_str = '';
for ($i=0; $i<$frombase; $i++) {
$frombase_str .= strtoupper(base_convert((string)$i, 10, 36));
}
 
$tobase_str = '';
for ($i=0; $i<$tobase; $i++) {
$tobase_str .= strtoupper(base_convert((string)$i, 10, 36));
}
 
$length = strlen($numstring);
$result = '';
$number = array();
for ($i = 0; $i < $length; $i++) {
$number[$i] = stripos($frombase_str, $numstring[$i]);
}
do { // Loop until whole number is converted
$divide = 0;
$newlen = 0;
for ($i = 0; $i < $length; $i++) { // Perform division manually (which is why this works with big numbers)
$divide = $divide * $frombase + $number[$i];
if ($divide >= $tobase) {
$number[$newlen++] = (int)($divide / $tobase);
$divide = $divide % $tobase;
} else if ($newlen > 0) {
$number[$newlen++] = 0;
}
}
$length = $newlen;
$result = $tobase_str[$divide] . $result; // Divide is basically $numstring % $tobase (i.e. the new character)
}
while ($newlen != 0);
 
return $result;
}
 
function bc_dec2bin($decimal_i) {
// https://www.exploringbinary.com/base-conversion-in-php-using-bcmath/
 
bcscale(0);
 
$binary_i = '';
do {
$binary_i = bcmod($decimal_i,'2') . $binary_i;
$decimal_i = bcdiv($decimal_i,'2');
} while (bccomp($decimal_i,'0'));
 
return $binary_i;
}
 
function bc_bin2dec($binary_i) {
// https://www.exploringbinary.com/base-conversion-in-php-using-bcmath/
 
bcscale(0);
 
$decimal_i = '0';
for ($i = 0; $i < strlen($binary_i); $i++) {
$decimal_i = bcmul($decimal_i,'2');
$decimal_i = bcadd($decimal_i,$binary_i[$i]);
}
 
return $decimal_i;
}
 
// ----------------- New functions -----------------
 
// Newly added: gmp_not / bcnot
function bcnot($a) {
bcscale(0);
// Convert $a to a binary string
$ab = bc_dec2bin($a);
$length = strlen($ab);
 
// Do the bitwise binary operation
$cb = '';
for ($i=0; $i<$length; $i++) {
$cb .= ($ab[$i] == 1) ? '0' : '1';
}
 
// Convert back to a decimal number
return bc_bin2dec($cb);
}
function gmp_not($a) {
bcscale(0);
return bcnot($a);
}
 
// Newly added: bcshiftl / gmp_shiftl
function bcshiftl($num, $bits) {
bcscale(0);
return bcmul($num, bcpow('2', $bits));
}
function gmp_shiftl($num, $bits) {
bcscale(0);
return bcshiftl($num, $bits);
}
 
// Newly added: bcshiftr / gmp_shiftr
function bcshiftr($num, $bits) {
bcscale(0);
return bcdiv($num, bcpow('2', $bits));
}
function gmp_shiftr($num, $bits) {
bcscale(0);
return bcshiftr($num, $bits);
}
 
// Newly added: bcfact (used by gmp_fact)
function bcfact($a) {
bcscale(0);
 
// Source: https://www.php.net/manual/de/book.bc.php#116510
 
if (!filter_var($a, FILTER_VALIDATE_INT) || $a <= 0) {
throw new InvalidArgumentException(sprintf('Argument must be natural number, "%s" given.', $a));
}
 
for ($result = '1'; $a > 0; $a--) {
$result = bcmul($result, $a);
}
 
return $result;
}
 
// Newly added (used by gmp_prob_prime, gmp_random_range and gmp_random_bits)
function bcrand($min, $max = false) {
bcscale(0);
// Source: https://github.com/CityOfZion/neo-php/blob/master/src/Crypto/BCMathUtils.php#L7
// Fixed: https://github.com/CityOfZion/neo-php/issues/16
if (!$max) {
$max = $min;
$min = 0;
}
return bcadd(bcmul(bcdiv((string)mt_rand(), (string)mt_getrandmax(), strlen($max)), bcsub(bcadd($max, '1'), $min)), $min);
}
 
// Newly added (used by gmp_random_seed)
function bcrand_seed($seed) {
bcscale(0);
mt_srand($seed);
}
}
/trunk/googlecache.inc.php
0,0 → 1,39
<?php
 
/*
* Google GetCache
* Copyright 2015 Daniel Marschall, ViaThinkSoft
* Version 2015-06-26
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
function google_getcache($url) {
$options = array(
'http'=>array(
'method'=>"GET",
'header'=>"Accept-language: en\r\n" .
"Cookie: foo=bar\r\n" . // check function.stream-context-create on php.net
"User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
)
);
 
$context = stream_context_create($options);
 
$url = 'https://www.google.de/search?q='.urlencode($url);
$cont = file_get_contents($url, false, $context);
preg_match_all('@(http://webcache.googleusercontent.com/.+)"@ismU', $cont, $m);
if (!isset($m[1][0])) return false;
$url = urldecode($m[1][0]);
return file_get_contents($url, false, $context);
}
/trunk/grep_funcs.inc.php
0,0 → 1,41
<?php
 
/*
* Grep functions for PHP
* Copyright 2012-2013 Daniel Marschall, ViaThinkSoft
* Version 2013-03-08
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
# TODO: if console available, use it
 
// "grep"
function grep(&$array, $substr) {
if (!is_array($array)) return false;
$ret = array();
foreach ($array as &$a) {
if (strpos($a, $substr) !== false) $ret[] = $a;
}
return $ret;
}
 
// "grep -v"
function antigrep(&$array, $substr) {
if (!is_array($array)) return false;
$ret = array();
foreach ($array as &$a) {
if (strpos($a, $substr) === false) $ret[] = $a;
}
return $ret;
}
/trunk/htmlentities_compat.inc.php
0,0 → 1,41
<?php
 
/*
* HtmlEntities compatibility functions
* Copyright 2019 Daniel Marschall, ViaThinkSoft
* Version 2019-11-18
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
# http://www.ufive.unibe.ch/index.php?c=php54entitiesfix&l=de
# This workaround is not required with PHP 5.6+, since htmlentities() now uses the default encoding charset as default parameter value
 
if (!function_exists('compat_htmlspecialchars')) {
function compat_htmlspecialchars($string, $ent=ENT_COMPAT, $charset='ISO-8859-1') {
return htmlspecialchars($string, $ent, $charset);
}
}
 
if (!function_exists('compat_htmlentities')) {
function compat_htmlentities($string, $ent=ENT_COMPAT, $charset='ISO-8859-1') {
return htmlentities($string, $ent, $charset);
}
}
 
if (!function_exists('compat_html_entity_decode')) {
function compat_html_entity_decode($string, $ent=ENT_COMPAT, $charset='ISO-8859-1') {
return html_entity_decode($string, $ent, $charset);
}
}
 
/trunk/ip_functions.inc.php
0,0 → 1,58
<?php
 
/*
* IP functions
* Copyright 2015-2022 Daniel Marschall, ViaThinkSoft
* Version 2021-01-07
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
// Attention in re $allow_proxy: It is not secure to use these, since they are not validated: http://www.thespanner.co.uk/2007/12/02/faking-the-unexpected/
function get_real_ip($allow_proxy=false) {
/* Eindeutige IP Adresse erhalten, auch bei Proxies und (neu:) von SSH connections im CLI modus */
// http://lists.phpbar.de/pipermail/php/Week-of-Mon-20040322/007749.html
// Modificated by VTS
// Version: 2021-01-07
 
// TODO: ipv6
 
if (isset($_SERVER['SSH_CLIENT'])) { $ary = explode(' ', $_SERVER['SSH_CLIENT']); return $ary[0]; }
if (isset($_SERVER['SSH_CONNECTION'])) { $ary = explode(' ', $_SERVER['SSH_CONNECTION']); return $ary[0]; }
 
$client_ip = ($allow_proxy && isset($_SERVER['HTTP_CLIENT_IP'])) ? $_SERVER['HTTP_CLIENT_IP'] : '';
$x_forwarded_for = ($allow_proxy && isset($_SERVER['HTTP_X_FORWARDED_FOR'])) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : '';
$remote_addr = (isset($_SERVER['REMOTE_ADDR'])) ? $_SERVER['REMOTE_ADDR'] : '';
 
if (!empty($client_ip)) {
$ip_expl = explode('.', $client_ip);
$referer = explode('.', $remote_addr);
if ($referer[0] != $ip_expl[0]) {
$ip = array_reverse($ip_expl);
$return = implode('.', $ip);
} else {
$return = $client_ip;
}
} else if (!empty($x_forwarded_for)) {
if (strstr($x_forwarded_for, ',')) {
$ip_expl = explode(',', $x_forwarded_for);
$return = end($ip_expl);
} else {
$return = $x_forwarded_for;
}
} else {
$return = $remote_addr;
}
unset ($client_ip, $x_forwarded_for, $remote_addr, $ip_expl);
return $return;
}
/trunk/ip_functions_todo
0,0 → 1,8
bitfuncs
mask funcs
 
funktion, um etwas aus einem $data herauszunehmen, achtung: [3-6] ohne [3-4] wird zu [5-6], achtung [0-100] ohne [3-5] wird zu [0-2] und [6-100]
 
ipv4: alle funktionen sollen incomplete akzeptieren
[$topip, $baseip] alternative auch einen $rng[0,1] akzeptieren
 
/trunk/ipresolution.inc.php
0,0 → 1,104
<?php
 
/*
* IP resolution functions
* Copyright 2012 Daniel Marschall, ViaThinkSoft
* Version 2012-02-02
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
/* -- Testcases --
print_r(gethostbynamel6('example.com'));
print_r(gethostbynamel6('ipv6.google.com'));
print_r(gethostbynamel6('google.de'));
print_r(gethostbynamel6('ipv6.google.de'));
print_r(gethostbynamel6('abc'));
print_r(gethostbynamel6('111.111.111.111'));
print_r(gethostbynamel6('2620::2d0:200:0:0:0:10'));
*/
 
function resolveip($host) {
return gethostbynamel6($host);
}
 
# http://www.php.net/manual/en/function.gethostbyname.php#70936
# Modified by ViaThinkSoft
 
# VTS-Modified: try_a default false -> true
function gethostbyname6($host, $try_a = /* false */ true) {
// get AAAA record for $host
// if $try_a is true, if AAAA fails, it tries for A
// the first match found is returned
// otherwise returns false
 
$dns = gethostbynamel6($host, $try_a);
if ($dns == false) {
return false;
} else {
return $dns[0];
}
}
 
# VTS-Modified: try_a default false -> true
function gethostbynamel6($host, $try_a = /* false */ true) {
# Added by VTS
$ipfilter = filter_var($host,FILTER_VALIDATE_IP);
if ($ipfilter != '') return array($ipfilter);
 
// get AAAA records for $host,
// if $try_a is true, if AAAA fails, it tries for A
// results are returned in an array of ips found matching type
// otherwise returns false
 
$dns6 = dns_get_record($host, DNS_AAAA);
if ($try_a == true) {
$dns4 = dns_get_record($host, DNS_A);
$dns = array_merge($dns4, $dns6);
} else {
$dns = $dns6;
}
$ip6 = array();
$ip4 = array();
foreach ($dns as $record) {
if ($record["type"] == "A") {
$ip4[] = $record["ip"];
}
if ($record["type"] == "AAAA") {
$ip6[] = $record["ipv6"];
}
}
 
# VTS-Modified: Output IP4+IP6 merged instead of giving only IPv6 or IPv4
$merged = array_merge($ip4, $ip6);
if (count($merged) < 1) {
return false;
} else {
return $merged;
}
/*
if (count($ip6) < 1) {
if ($try_a == true) {
if (count($ip4) < 1) {
return false;
} else {
return $ip4;
}
} else {
return false;
}
} else {
return $ip6;
}
*/
}
/trunk/ipv4_functions.inc.php
0,0 → 1,661
<?php
 
/*
* IPv4 functions for PHP
* Copyright 2012-2022 Daniel Marschall, ViaThinkSoft
* Version 2022-09-22
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
// TODO: oop, exceptions?
 
// Very small self-test:
/*
function ipv4_selftest() {
$iv_b = ipv4_complete('1.2');
$iv_m = 20;
$r = ipv4_cidr2range($iv_b, $iv_m);
echo "$iv_b/$iv_m => $r[0] - $r[1]\n";
 
$rev = ipv4_range2cidr($r[0], $r[1]);
$rev = implode("\n", $rev);
echo "$r[0] - $r[1] => $rev [";
$ok = $rev == "$iv_b/$iv_m";
echo $ok ? 'OK' : 'Mismatch';
echo "]\n";
echo "In-CIDR-Test: ";
echo ipv4_in_cidr("$iv_b/$iv_m", "$iv_b/$iv_m") ? 'OK' : 'Fail';
echo "\n";
}
ipv4_selftest();
*/
 
function ipv4_cidr2range($baseip_or_cidr, $subnet='') {
# (C) 2012 ViaThinkSoft
# Version 1.1
# This function converts an CIDR notation <baseip>/<subnet> into an IPv4 address block array($low_ip, $high_ip)
 
if (strpos($baseip_or_cidr, '/') !== false) {
$tmp = explode('/', $baseip_or_cidr, 2);
$baseip_or_cidr = $tmp[0];
$subnet = $tmp[1];
unset($tmp);
}
 
if (($subnet < 0) || ($subnet > 32)) return false;
 
$maxint32 = 0xFFFFFFFF;
$netmask = $maxint32 << (32-$subnet);
$netmask = $netmask & $maxint32; // crop to 32 bits
$wildcard = $maxint32 ^ $netmask; // ~$netmask;
 
$x = ipv4_incomplete_ip2long($baseip_or_cidr) & $netmask;
$nums = $wildcard;
$low = long2ip($x);
$high = long2ip($x + $nums);
 
return array($low, $high);
}
 
function ipv4_range2cidr($baseip, $topip, $shortening=false) {
# (C) 2012 ViaThinkSoft
# Version 1.0
# This function converts an IPv4 address block into valid CIDR blocks (There may be multiple blocks!)
 
$out = array();
if (ipv4_cmp($baseip, $topip) > 0) return false;
while (ipv4_incomplete_ip2long($baseip)-1 != ipv4_incomplete_ip2long($topip)) {
$i = -1;
do {
$i++;
$range = ipv4_cidr2range($baseip, $i);
$l = $range[0];
$t = $range[1];
} while ((ipv4_cmp($l, $baseip) != 0) || (ipv4_cmp($t, $topip) > 0));
 
# Shortening: Stroke ".0" at the end
if ($shortening) $baseip = ipv4_shortening($baseip);
 
$out[] = "$baseip/$i";
$baseip = ipv4_add($t, 1);
}
return $out;
}
 
function ipv4_shortening($ip) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
return preg_replace("|(\\.0{1,3}){0,3}\$|ismU", '', $ip);
}
 
function ipv4_add($baseip, $num) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
return long2ip(ipv4_incomplete_ip2long($baseip) + $num);
}
 
function ipv4_sub($baseip, $num) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
return long2ip(ipv4_incomplete_ip2long($baseip) - $num);
}
 
function ipv4_cmp($a, $b) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
$a = ipv4_incomplete_ip2long($a);
$b = ipv4_incomplete_ip2long($b);
 
if ($a == $b) return 0;
if ($a < $b) return -1;
if ($a > $b) return 1;
}
 
function ipv4_in_cidr($haystack, $needle) {
# (C) 2012 ViaThinkSoft
# Version 1.1
 
$x = explode('/', $haystack);
$ha = ipv4_cidr2range($x[0], $x[1]);
 
$x = explode('/', $needle);
if (!isset($x[1])) $x[1] = '32'; // single IP
$ne = ipv4_cidr2range($x[0], $x[1]);
 
$ha_low = ipv4_incomplete_ip2long($ha[0]);
$ha_hig = ipv4_incomplete_ip2long($ha[1]);
$ne_low = ipv4_incomplete_ip2long($ne[0]);
$ne_hig = ipv4_incomplete_ip2long($ne[1]);
 
# HA: low[ ]high
# NE: low[ ]high
 
return ($ne_low >= $ha_low) && ($ne_hig <= $ha_hig);
}
 
function ipv4_complete($short_form) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
$short_form = trim($short_form);
if ($short_form == '') return '0.0.0.0';
$c = substr_count($short_form, '.');
if ($c > 3) return false;
if ($c == 3) return $short_form;
$c = substr_count($short_form, '.');
$short_form .= str_repeat('.0', 3-$c);
return $short_form;
}
 
function ipv4_incomplete_ip2long($ip) {
# (C) 2012-2014 ViaThinkSoft
# Version 1.2
 
# return sprintf('%u', ip2long(ipv4_complete($ip)));
return sprintf('%u', ip2long(ipv4_normalize($ip)));
}
 
// IMPORTANT! $cmp_ary[x]=y MUST HAVE x<=y !
function ipv4_merge_address_blocks($data, $debug = false, $shortening = false) {
# (C) 2012-2013 ViaThinkSoft
# Version 2.2
 
if ($debug !== false) $STARTZEIT = time();
 
// 1. Convert IPs to numbers
 
$cmp_ary = array();
foreach ($data as $a => &$b) {
$a = ipv4_incomplete_ip2long($a);
$b = ipv4_incomplete_ip2long($b);
 
$cmp_ary[$a] = $b;
unset($a);
unset($b);
}
 
// 2. Sort array
 
ksort($cmp_ary);
 
// 3. Merge the blocks in an intelligent way (and remove redundant blocks)
 
# Merge overlapping blocks
# [ ]
# [ ] -> [ ]
 
# Merge neighbor blocks
# [ ][ ] -> [ ]
 
# Remove redundant blocks
# [ ] -> [ ]
# [ ]
 
$merge_count = 0;
$redundant_deleted_count = 0;
$round_count = 0;
do {
if ($debug !== false) {
$LAUFZEIT = time() - $STARTZEIT;
echo $debug."Merging... $round_count rounds; merged $merge_count blocks; deleted $redundant_deleted_count redundant blocks; time: $LAUFZEIT seconds\r";
}
 
$round_count++;
 
$clean = true;
 
foreach ($cmp_ary as $a => &$b) {
foreach ($cmp_ary as $x => &$y) {
// x in range [a+1..b+1] ?
if ($x<=$a) continue;
if ($x>$b+1) break;
 
// Merge
$clean = false;
if ($y>$b) {
$merge_count++;
$b = $y;
unset($cmp_ary[$x]);
} else {
$redundant_deleted_count++;
unset($cmp_ary[$x]);
}
}
}
} while (!$clean);
 
if ($debug !== false) {
$LAUFZEIT = time() - $STARTZEIT;
echo $debug."Merge completed. $round_count rounds; merged $merge_count blocks; deleted $redundant_deleted_count redundant blocks; time: $LAUFZEIT seconds\n";
}
 
// 4. Convert back to IPs
 
$out_ary = array();
foreach ($cmp_ary as $a => &$b) {
$a = long2ip($a);
$b = long2ip($b);
if ($shortening) {
$a = ipv4_shortening($a);
$b = ipv4_shortening($b);
}
$out_ary[$a] = $b;
}
 
return $out_ary;
}
 
function ipv4_merge_arrays($data_a, $data_b) {
# (C) 2012 ViaThinkSoft
# Version 1.2
 
$normalized_data_a = array();
foreach ($data_a as $from => &$to) {
$normalized_data_a[ipv4_normalize($from)] = ipv4_normalize($to);
}
 
$normalized_data_b = array();
foreach ($data_b as $from => &$to) {
$normalized_data_b[ipv4_normalize($from)] = ipv4_normalize($to);
}
 
$data = array();
 
foreach ($normalized_data_a as $from => &$to) {
if (isset($normalized_data_b[$from])) {
$data[$from] = ipv4_max($to, $normalized_data_b[$from]);
} else {
$data[$from] = $to;
}
}
 
foreach ($normalized_data_b as $from => &$to) {
if (!isset($normalized_data_a[$from])) {
$data[$from] = $to;
}
}
 
return $data;
}
 
function ipv4_valid($ip) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
# return ipv4_incomplete_ip2long($ip) !== false;
return ip2long($ip) !== false;
}
 
function ipv4_normalize($ip) {
# (C) 2012-2013 ViaThinkSoft
# Version 1.1.1
 
# Example:
# 100.010.001.000 -> 100.10.1.0
 
$ip = ipv4_complete($ip);
if (!$ip) return false;
 
# ip2long buggy: 001.0.0.0 is not accepted
## $cry = explode('.', $ip);
## $cry[0] = preg_replace('@^0+@', '', $cry[0]); if ($cry[0] == '') $cry[0] = '0';
## $cry[1] = preg_replace('@^0+@', '', $cry[1]); if ($cry[1] == '') $cry[1] = '0';
## $cry[2] = preg_replace('@^0+@', '', $cry[2]); if ($cry[2] == '') $cry[2] = '0';
## $cry[3] = preg_replace('@^0+@', '', $cry[3]); if ($cry[3] == '') $cry[3] = '0';
## $ip = implode('.', $cry);
## return $ip;
 
return preg_replace('@^0{0,2}([0-9]{1,3})\.0{0,2}([0-9]{1,3})\.0{0,2}([0-9]{1,3})\.0{0,2}([0-9]{1,3})$@', '\\1.\\2.\\3.\\4', $ip);
}
 
function ipv4_expand($ip) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
# Example:
# 100.10.1.0 -> 100.010.001.000
 
$ip = ipv4_complete($ip);
if (!$ip) return false;
 
$cry = explode('.', $ip);
$cry[0] = str_pad($cry[0], 3, '0', STR_PAD_LEFT);
$cry[1] = str_pad($cry[1], 3, '0', STR_PAD_LEFT);
$cry[2] = str_pad($cry[2], 3, '0', STR_PAD_LEFT);
$cry[3] = str_pad($cry[3], 3, '0', STR_PAD_LEFT);
return implode('.', $cry);
}
 
function ipv4_min($ip_a, $ip_b) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
if (ipv4_cmp($ip_a, $ip_b) == -1) {
return $ip_a;
} else {
return $ip_b;
}
}
 
function ipv4_max($ip_a, $ip_b) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
if (ipv4_cmp($ip_a, $ip_b) == 1) {
return $ip_a;
} else {
return $ip_b;
}
}
 
function ipv4_ipcount($data) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
$cnt = 0;
 
foreach ($data as $from => &$to) {
$cnt += ipv4_incomplete_ip2long($to) - ipv4_incomplete_ip2long($from);
}
 
return $cnt;
}
 
function ipv4_read_file($file) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
$data = array();
 
$lines = file($file);
foreach ($lines as &$line) {
$rng = ipv4_line2range($line);
$data[$rng[0]] = $rng[1];
}
 
return $data;
}
 
function ipv4_line2range($line) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
$line = trim($line);
 
if (strpos($line, '/') !== false) {
$rng = ipv4_cidr2range($line);
} else {
$rng = explode('-', $line);
$rng[0] = ipv4_normalize(trim($rng[0]));
$rng[1] = isset($rng[1]) ? ipv4_normalize(trim($rng[1])) : $rng[0];
}
 
return $rng;
}
 
# --- New 16,12,12
 
define('IPV4_BITS', 32);
 
function ipv4_distance($ipOrCIDR_Searchterm, $ipOrCIDR_Candidate) {
$ary = ipv4_cidr_split($ipOrCIDR_Searchterm);
$ip = $ary[0];
 
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false) {
return false;
}
 
$ary = ipv4_cidr_split($ipOrCIDR_Candidate);
$ip = $ary[0];
$cidr_bits = $ary[1];
if ($cidr_bits > IPV4_BITS) return false; // throw new Exception('CIDR bits > '.IPV4_BITS);
if (!is_numeric($cidr_bits)) return false;
 
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false) {
return false;
}
 
$x = ipv4_trackdown($ipOrCIDR_Searchterm);
 
if (ipv4_in_cidr($x[0], $ip.'/'.$cidr_bits)) {
$ary = ipv4_cidr_split($x[0]);
$cidr_bits2 = $ary[1];
if ($cidr_bits2 > IPV4_BITS) return false; // throw new Exception('CIDR bits > '.IPV4_BITS);
return $cidr_bits2-$cidr_bits;
}
 
$i = 0;
$max = false;
foreach ($x as &$y) {
if (ipv4_in_cidr($ip.'/'.$cidr_bits, $y)) {
$max = $i;
}
$i++;
}
 
return $max;
}
 
function ipv4_cidr_split($ipOrCIDR) {
$ary = explode('/', $ipOrCIDR, 2);
$cidr_bits = isset($ary[1]) ? $ary[1] : IPV4_BITS;
if ($cidr_bits > IPV4_BITS) return false; // throw new Exception('CIDR bits > '.IPV4_BITS);
if (!is_numeric($cidr_bits)) return false;
$ip = $ary[0];
return array($ip, $cidr_bits);
}
 
function ipv4_equals($ipOrCIDRA, $ipOrCIDRB) {
return ipv4_normalize_range($ipOrCIDRA) == ipv4_normalize_range($ipOrCIDRB);
}
 
function ipv4_cidr_min_ip($ipOrCIDR) {
$ary = ipv4_cidr_split($ipOrCIDR);
$ipOrCIDR = $ary[0];
$cidr_bits = $ary[1];
if ($cidr_bits > IPV4_BITS) return false; // throw new Exception('CIDR bits > '.IPV4_BITS);
if (!is_numeric($cidr_bits)) return false;
 
$m = ip2bin($ipOrCIDR);
$m = substr($m, 0, $cidr_bits) . str_repeat('0', IPV4_BITS-$cidr_bits);
 
return bin2ip($m);
}
 
function ipv4_cidr_max_ip($ipOrCIDR) {
$ary = ipv4_cidr_split($ipOrCIDR);
$ipOrCIDR = $ary[0];
$cidr_bits = $ary[1];
if ($cidr_bits > IPV4_BITS) return false; // throw new Exception('CIDR bits > '.IPV4_BITS);
if (!is_numeric($cidr_bits)) return false;
 
$m = ip2bin($ipOrCIDR);
$m = substr($m, 0, $cidr_bits) . str_repeat('1', IPV4_BITS-$cidr_bits);
 
return bin2ip($m);
}
 
function ipv4_normalize_range($ipOrCIDR) {
$ary = ipv4_cidr_split($ipOrCIDR);
$ipOrCIDR = $ary[0];
$cidr_bits = $ary[1];
if ($cidr_bits > IPV4_BITS) return false; // throw new Exception('CIDR bits > '.IPV4_BITS);
if (!is_numeric($cidr_bits)) return false;
 
$m = ip2bin($ipOrCIDR);
$m = substr($m, 0, $cidr_bits) . str_repeat('0', IPV4_BITS-$cidr_bits);
 
return bin2ip($m) . '/' . $cidr_bits;
}
 
function ipv4_trackdown($ipOrCIDR) {
$ary = ipv4_cidr_split($ipOrCIDR);
$ipOrCIDR = $ary[0];
$cidr_bits = $ary[1];
if ($cidr_bits > IPV4_BITS) return false; // throw new Exception('CIDR bits > '.IPV4_BITS);
if (!is_numeric($cidr_bits)) return false;
 
$out = array();
$m = ip2bin($ipOrCIDR);
 
for ($i=$cidr_bits; $i>=0; $i--) {
$m = substr($m, 0, $i) . str_repeat('0', IPV4_BITS-$i);
$out[] = bin2ip($m) . '/' . $i;
}
 
return $out;
}
 
# ---
 
if (!function_exists('ip2bin')) {
function ip2bin($ip) {
# Source: http://php.net/manual/en/function.ip2long.php#104163
# modified by VTS
 
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) {
$iplong = ip2long($ip);
assert($iplong !== false);
return base_convert((string)$iplong, 10, 2);
}
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
return false;
}
if (($ip_n = inet_pton($ip)) === false) {
return false;
}
$bits = 15; // 16 x 8 bit = 128bit (ipv6)
$ipbin = ''; # added by vts to avoid warning
while ($bits >= 0) {
$bin = sprintf('%08b', (ord($ip_n[$bits])));
$ipbin = $bin.$ipbin;
$bits--;
}
return $ipbin;
}
}
 
if (!function_exists('bin2ip')) {
function bin2ip($bin) {
# Source: http://php.net/manual/en/function.ip2long.php#104163
# modified by VTS
 
if (strlen($bin) <= 32) { // 32bits (ipv4)
$iplong = base_convert($bin, 2, 10);
return long2ip(intval($iplong));
}
if (strlen($bin) != 128) {
return false;
}
//$bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
$bits = 0;
$ipv6 = ''; # added by vts to avoid warning
while ($bits <= 7) {
$bin_part = substr($bin,($bits*16),16);
$ipv6 .= dechex(bindec($bin_part)) . ':';
$bits++;
}
return inet_ntop(inet_pton(substr($ipv6, 0, -1)));
}
}
 
# --- TEST
 
/*
assert(ipv4_normalize('100.010.001.000') == '100.10.1.0');
assert(ipv4_normalize('100.010.01.000') == '100.10.1.0');
assert(ipv4_normalize('100.10.001.000') == '100.10.1.0');
assert(ipv4_normalize('1.010.001.000') == '1.10.1.0');
assert(ipv4_normalize('1.10.001.000') == '1.10.1.0');
 
assert(ipv4_distance('192.168.0.0/16', '192.168.64.0/18') == -2);
assert(ipv4_distance('192.168.0.0/17', '192.168.64.0/18') == -1);
assert(ipv4_distance('192.168.64.0/18', '192.168.64.0/18') == 0);
assert(ipv4_distance('192.168.64.0/19', '192.168.64.0/18') == 1);
assert(ipv4_distance('192.168.64.0/20', '192.168.64.0/18') == 2);
 
assert(ipv4_distance('192.168.69.202/31', '192.168.69.200/31') === false);
assert(ipv4_distance('192.168.69.201/32', '192.168.69.200/32') === false);
assert(ipv4_distance('192.168.69.201', '192.168.69.200') === false);
*/
 
/*
$test = '192.168.69.123';
$x = ipv4_trackdown($test);
foreach ($x as &$cidr) {
$min = ipv4_cidr_min_ip($cidr);
$max = ipv4_cidr_max_ip($cidr);
echo "$cidr ($min - $max)\n";
}
*/
 
 
 
 
function ipv4_sort($ary) {
$f = array();
foreach ($ary as $c) {
$a = explode('/', $c);
$ip = $a[0];
$bits = isset($a[1]) ? $a[1] : 32;
 
$d = ip2bin($ip);
 
# ord('*') must be smaller than ord('0')
$d = substr($d, 0, $bits).str_repeat('*', 32-$bits);
 
$f[$d] = $c;
}
 
return $f;
}
 
function ipv4_make_tree($ary) {
$ary = ipv4_sort($ary);
 
if (count($ary) == 0) return array();
 
$sub_begin = '';
$sub_begin_ip = '';
foreach ($ary as $n => $d) {
$sub_begin = substr($n, 0, strpos($n, '*'));
$sub_begin_ip = $d;
unset($ary[$n]);
break;
}
 
$sub = array();
$nonsub = array();
foreach ($ary as $n => $d) {
if (substr($n, 0, strlen($sub_begin)) == $sub_begin) {
$sub[$n] = $d;
} else {
$nonsub[$n] = $d;
}
}
 
$out = array();
$out[$sub_begin_ip] = ipv4_make_tree($sub);
 
$a = ipv4_make_tree($nonsub);
 
$out = array_merge($out, $a);
 
return $out;
}
 
/trunk/ipv6_functions.inc.php
0,0 → 1,758
<?php
 
/*
* IPv6 functions for PHP
* Copyright 2012-2022 Daniel Marschall, ViaThinkSoft
* Version 2022-09-22
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
# This library requires either the GMP extension (or BCMath if gmp_supplement.inc.php is present)
 
// TODO: oop, exceptions?
// TODO: variant without gmp ?
// TODO: IPv6 resolution 'ffff::192.168.69.1' -> 'ffff:0000:0000:0000:0000:0000:c0a8:4501' does not work!
 
if (file_exists(__DIR__ . '/gmp_supplement.inc.php')) include_once __DIR__ . '/gmp_supplement.inc.php';
 
define('GMP_ONE', gmp_init('1'));
 
// Very small self-test:
/*
function ipv6_selftest() {
$iv_b = 'c0ff:ee00::';
$iv_m = 32;
$r = ipv6_cidr2range($iv_b, $iv_m);
echo "$iv_b/$iv_m => $r[0] - $r[1]\n";
 
$rev = ipv6_range2cidr($r[0], $r[1]);
$rev = implode("\n", $rev);
echo "$r[0] - $r[1] => $rev [";
$ok = $rev == "$iv_b/$iv_m";
echo $ok ? 'OK' : 'Mismatch';
echo "]\n";
echo "In-CIDR-Test: ";
echo ipv6_in_cidr("$iv_b/$iv_m", "$iv_b/$iv_m") ? 'OK' : 'Fail';
echo "\n";
}
ipv6_selftest();
*/
 
$cache_ipv6_cidr2range = array();
function ipv6_cidr2range($baseip_or_cidr, $subnet='') {
# (C) 2012 ViaThinkSoft
# Version 1.1
# This function converts an CIDR notation <baseip>/<subnet> into an IPv6 address block array($low_ip, $high_ip)
 
global $cache_ipv6_cidr2range;
$vvv = $baseip_or_cidr.'|'.$subnet;
if (isset($cache_ipv6_cidr2range[$vvv])) return $cache_ipv6_cidr2range[$vvv];
 
if (strpos($baseip_or_cidr, '/') !== false) {
$tmp = explode('/', $baseip_or_cidr, 2);
$baseip_or_cidr = $tmp[0];
$subnet = $tmp[1];
unset($tmp);
}
 
if (($subnet < 0) || ($subnet > 128)) {
$cache_ipv6_cidr2range[$vvv] = false;
return false;
}
 
$maxint128 = gmp_sub(gmp_pow('2', 128), GMP_ONE); # TODO: GMP_TWO ?
$netmask = gmp_shiftl($maxint128, 128-$subnet);
$netmask = gmp_and($netmask, $maxint128); // crop to 128 bit
$wildcard = gmp_xor($maxint128, $netmask);
 
$x = gmp_and(ip2long6($baseip_or_cidr), $netmask);
$nums = $wildcard;
$low = long2ip6($x);
$high = long2ip6(gmp_add($x, $nums));
 
$out = array($low, $high);
$cache_ipv6_cidr2range[$vvv] = $out;
return $out;
}
 
$cache_ipv6_range2cidr = array();
function ipv6_range2cidr($baseip, $topip) {
# (C) 2012 ViaThinkSoft
# Version 1.0
# This function converts an IPv6 address block into valid CIDR blocks (There may be multiple blocks!)
 
global $cache_ipv6_range2cidr;
$vvv = $baseip.'|'.$topip;
if (isset($cache_ipv6_range2cidr[$vvv])) return $cache_ipv6_range2cidr[$vvv];
 
$out = array();
if (ipv6_cmp($baseip, $topip) > 0) {
$cache_ipv6_range2cidr[$vvv] = false;
return false;
}
while (gmp_cmp(gmp_sub(ip2long6($baseip), GMP_ONE), ip2long6($topip)) != 0) {
$i = -1;
do {
$i++;
$range = ipv6_cidr2range($baseip, $i);
$l = $range[0];
$t = $range[1];
} while ((ipv6_cmp($l, $baseip) != 0) || (ipv6_cmp($t, $topip) > 0));
 
$out[] = "$baseip/$i";
$baseip = ipv6_add($t, GMP_ONE);
}
 
$cache_ipv6_range2cidr[$vvv] = $out;
return $out;
}
 
function ipv6_add($baseip, $num) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
return long2ip6(gmp_add(ip2long6($baseip), $num));
}
 
function ipv6_sub($baseip, $num) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
return long2ip6(gmp_sub(ip2long6($baseip), $num));
}
 
function ipv6_cmp($a, $b) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
return gmp_cmp(ip2long6($a), ip2long6($b));
}
 
$cache_ipv6_in_cidr = array();
function ipv6_in_cidr($haystack, $needle) {
# (C) 2012 ViaThinkSoft
# Version 1.1
 
global $cache_ipv6_in_cidr;
$vvv = $haystack.'|'.$needle;
if (isset($cache_ipv6_in_cidr[$vvv])) return $cache_ipv6_in_cidr[$vvv];
 
$x = explode('/', $haystack);
$ha = ipv6_cidr2range($x[0], $x[1]);
 
$x = explode('/', $needle);
if (!isset($x[1])) $x[1] = 128; // single IP
$ne = ipv6_cidr2range($x[0], $x[1]);
 
$ha_low = ip2long6($ha[0]);
$ha_hig = ip2long6($ha[1]);
$ne_low = ip2long6($ne[0]);
$ne_hig = ip2long6($ne[1]);
 
# HA: low[ ]high
# NE: low[ ]high
 
$out = (gmp_cmp($ne_low, $ha_low) >= 0) && (gmp_cmp($ne_hig, $ha_hig) <= 0);
$cache_ipv6_in_cidr[$vvv] = $out;
return $out;
}
 
// IMPORTANT! $cmp_ary[x]=y MUST HAVE x<=y !
function ipv6_merge_address_blocks($data, $debug = false) {
# (C) 2012-2013 ViaThinkSoft
# Version 2.2
 
if ($debug !== false) $STARTZEIT = time();
 
// 1. Convert IPs to numbers
 
$cmp_ary = array();
foreach ($data as $a => &$b) {
$a = ip2long6($a);
$b = ip2long6($b);
 
$cmp_ary[gmp_strval($a)] = gmp_strval($b);
unset($a);
unset($b);
}
 
// 2. Sort array
 
ksort($cmp_ary);
 
// 3. Merge the blocks in an intelligent way (and remove redundant blocks)
 
# Merge overlapping blocks
# [ ]
# [ ] -> [ ]
 
# Merge neighbor blocks
# [ ][ ] -> [ ]
 
# Remove redundant blocks
# [ ] -> [ ]
# [ ]
 
$merge_count = 0;
$redundant_deleted_count = 0;
$round_count = 0;
do {
if ($debug !== false) {
$LAUFZEIT = time() - $STARTZEIT;
echo $debug."Merging... $round_count rounds; merged $merge_count blocks; deleted $redundant_deleted_count redundant blocks; time: $LAUFZEIT seconds\r";
}
 
$round_count++;
 
$clean = true;
 
foreach ($cmp_ary as $a => &$b) {
foreach ($cmp_ary as $x => &$y) {
// x in range [a+1..b+1] ?
if (gmp_cmp(gmp_init($x), gmp_init($a)) <= 0) continue;
if (gmp_cmp(gmp_init($x), gmp_add(gmp_init($b), GMP_ONE)) > 0) break;
 
// Merge
$clean = false;
if (gmp_cmp(gmp_init($y), gmp_init($b)) > 0) {
$merge_count++;
$b = $y;
unset($cmp_ary[$x]);
} else {
$redundant_deleted_count++;
unset($cmp_ary[$x]);
}
}
}
} while (!$clean);
 
if ($debug !== false) {
$LAUFZEIT = time() - $STARTZEIT;
echo $debug."Merge completed. $round_count rounds; merged $merge_count blocks; deleted $redundant_deleted_count redundant blocks; time: $LAUFZEIT seconds\n";
}
 
// 4. Convert back to IPs
 
$out_ary = array();
foreach ($cmp_ary as $a => &$b) {
$a = long2ip6(gmp_init($a));
$b = long2ip6(gmp_init($b));
$out_ary[$a] = $b;
}
 
return $out_ary;
}
 
function ipv6_merge_arrays($data_a, $data_b) {
# (C) 2012 ViaThinkSoft
# Version 1.2
 
$normalized_data_a = array();
foreach ($data_a as $from => &$to) {
$normalized_data_a[ipv6_normalize($from)] = ipv6_normalize($to);
}
 
$normalized_data_b = array();
foreach ($data_b as $from => &$to) {
$normalized_data_b[ipv6_normalize($from)] = ipv6_normalize($to);
}
 
$data = array();
 
foreach ($normalized_data_a as $from => &$to) {
if (isset($normalized_data_b[$from])) {
$data[$from] = ipv6_max($to, $normalized_data_b[$from]);
} else {
$data[$from] = $to;
}
}
 
foreach ($normalized_data_b as $from => &$to) {
if (!isset($normalized_data_a[$from])) {
$data[$from] = $to;
}
}
 
return $data;
}
 
function ipv6_valid($ip) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
return ip2long6($ip) !== false;
}
 
function ipv6_normalize($ip) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
# Example:
# 2001:0000:0000::1 -> 2001::1
 
$long = ip2long6($ip);
if ($long == -1 || $long === FALSE) return false;
return long2ip6($long);
}
 
function ipv6_expand($ip) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
# Example:
# 2001::1 -> 2001:0000:0000:0000:0000:0000:0000:0000
 
$long = ip2long6($ip);
if ($long == -1 || $long === FALSE) return false;
return long2ip6($long, false);
}
 
function ipv6_min($ip_a, $ip_b) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
if (ipv6_cmp($ip_a, $ip_b) == -1) {
return $ip_a;
} else {
return $ip_b;
}
}
 
function ipv6_max($ip_a, $ip_b) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
if (ipv6_cmp($ip_a, $ip_b) == 1) {
return $ip_a;
} else {
return $ip_b;
}
}
 
function ipv6_ipcount($data) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
$cnt = gmp_init('0');
 
foreach ($data as $from => &$to) {
$cnt = gmp_add($cnt, gmp_sub(ip2long6($to), ip2long6($from)));
}
 
return gmp_strval($cnt, 10);
}
 
function ipv6_read_file($file) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
$data = array();
 
$lines = file($file);
foreach ($lines as &$line) {
$rng = ipv6_line2range($line);
$data[$rng[0]] = $rng[1];
}
 
return $data;
}
 
function ipv6_line2range($line) {
# (C) 2012 ViaThinkSoft
# Version 1.0
 
$line = trim($line);
 
if (strpos($line, '/') !== false) {
$rng = ipv6_cidr2range($line);
} else {
$rng = explode('-', $line);
$rng[0] = ipv6_normalize(trim($rng[0]));
$rng[1] = isset($rng[1]) ? ipv6_normalize(trim($rng[1])) : $rng[0];
}
 
return $rng;
}
 
# ---
 
if (!function_exists('gmp_shiftl')) {
function gmp_shiftl($x, $n) { // shift left
// http://www.php.net/manual/en/ref.gmp.php#99788
return gmp_mul($x, gmp_pow('2', $n));
}
}
 
if (!function_exists('gmp_shiftr')) {
function gmp_shiftr($x, $n) { // shift right
// http://www.php.net/manual/en/ref.gmp.php#99788
return gmp_div($x, gmp_pow('2', $n));
}
}
 
$cache_ip2long6 = array();
function ip2long6($ipv6) {
// Source:
// http://www.netz-guru.de/2009/11/07/php-ipv6-ip2long-und-long2ip-funktionen/
// Slightly modified
 
global $cache_ip2long6;
if (isset($cache_ip2long6[$ipv6])) return $cache_ip2long6[$ipv6];
 
if ($ipv6 == '') $ipv6 = '::';
 
if (filter_var($ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
$cache_ip2long6[$ipv6] = false;
return false;
}
 
$ip_n = @inet_pton($ipv6);
if ($ip_n === false) {
$cache_ip2long6[$ipv6] = false;
return false; // modified
}
$bytes = 16; // 16 bytes x 8 bit/byte = 128bit
$ipv6long = '';
 
while ($bytes > 0) {
$bin = sprintf('%08b',(ord($ip_n[$bytes-1])));
$ipv6long = $bin.$ipv6long;
$bytes--;
}
 
// $out = gmp_strval(gmp_init($ipv6long, 2), 10);
$out = gmp_init($ipv6long, 2);
$cache_ip2long6[$ipv6] = $out;
return $out;
}
 
$cache_long2ip6 = array();
function long2ip6($ipv6long, $compress=true) {
// Source:
// http://www.netz-guru.de/2009/11/07/php-ipv6-ip2long-und-long2ip-funktionen/
// Slightly modified
 
global $cache_long2ip6;
$vvv = ($compress ? 'T' : 'F').$ipv6long;
if (isset($cache_long2ip6[$vvv])) return $cache_long2ip6[$vvv];
 
// $bin = gmp_strval(gmp_init($ipv6long, 10), 2);
$bin = gmp_strval($ipv6long, 2);
$bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
 
$bytes = 0;
$ipv6 = '';
while ($bytes < 8) { // 16 bytes x 8 bit/byte = 128bit
$bin_part = substr($bin,($bytes*16),16);
$part = dechex(bindec($bin_part));
if (!$compress) {
$part = str_pad($part, 4, '0', STR_PAD_LEFT);
}
$ipv6 .= $part.':';
$bytes++;
}
 
if ($compress) {
$out = inet_ntop(inet_pton(substr($ipv6, 0, -1)));
} else {
$out = substr($ipv6, 0, strlen($ipv6)-1);
}
$cache_long2ip6[$vvv] = $out;
return $out;
}
 
# --- New 16,12,12
 
define('IPV6_BITS', 128);
 
$global_ipv6_distance = array();
function ipv6_distance($ipOrCIDR_Searchterm, $ipOrCIDR_Candidate) {
global $global_ipv6_distance;
$vvv = $ipOrCIDR_Searchterm.'|'.$ipOrCIDR_Candidate;
if (isset($global_ipv6_distance[$vvv])) return $global_ipv6_distance[$vvv];
 
$ary = ipv6_cidr_split($ipOrCIDR_Searchterm);
$ip = $ary[0];
 
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
$global_ipv6_distance[$vvv] = false;
return false;
}
 
$ary = ipv6_cidr_split($ipOrCIDR_Candidate);
$ip = $ary[0];
$cidr_bits = $ary[1];
if ($cidr_bits > IPV6_BITS) {
$global_ipv6_distance[$vvv] = false;
return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
}
if (!is_numeric($cidr_bits)) return false;
 
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
$global_ipv6_distance[$vvv] = false;
return false;
}
 
$x = ipv6_trackdown($ipOrCIDR_Searchterm);
 
if (ipv6_in_cidr($x[0], $ip.'/'.$cidr_bits)) {
$ary = ipv6_cidr_split($x[0]);
$cidr_bits2 = $ary[1];
if ($cidr_bits2 > IPV6_BITS) {
$global_ipv6_distance[$vvv] = false;
return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
}
$out = $cidr_bits2-$cidr_bits;
$global_ipv6_distance[$vvv] = $out;
return $out;
}
 
$i = 0;
$max = false;
foreach ($x as &$y) {
if (ipv6_in_cidr($ip.'/'.$cidr_bits, $y)) {
$max = $i;
}
$i++;
}
 
$global_ipv6_distance[$vvv] = $max;
return $max;
}
 
function ipv6_cidr_split($ipOrCIDR) {
$ary = explode('/', $ipOrCIDR, 2);
$cidr_bits = isset($ary[1]) ? $ary[1] : IPV6_BITS;
if ($cidr_bits > IPV6_BITS) return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
if (!is_numeric($cidr_bits)) return false;
$ip = $ary[0];
return array($ip, $cidr_bits);
}
 
function ipv6_equals($ipOrCIDRA, $ipOrCIDRB) {
return ipv6_normalize_range($ipOrCIDRA) == ipv6_normalize_range($ipOrCIDRB);
}
 
function ipv6_cidr_min_ip($ipOrCIDR) {
$ary = ipv6_cidr_split($ipOrCIDR);
$ipOrCIDR = $ary[0];
$cidr_bits = $ary[1];
if ($cidr_bits > IPV6_BITS) return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
if (!is_numeric($cidr_bits)) return false;
 
$m = ip2bin($ipOrCIDR);
$m = substr($m, 0, $cidr_bits) . str_repeat('0', IPV6_BITS-$cidr_bits);
 
return bin2ip($m);
}
 
function ipv6_cidr_max_ip($ipOrCIDR) {
$ary = ipv6_cidr_split($ipOrCIDR);
$ipOrCIDR = $ary[0];
$cidr_bits = $ary[1];
if ($cidr_bits > IPV6_BITS) return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
if (!is_numeric($cidr_bits)) return false;
 
$m = ip2bin($ipOrCIDR);
$m = substr($m, 0, $cidr_bits) . str_repeat('1', IPV6_BITS-$cidr_bits);
 
return bin2ip($m);
}
 
function ipv6_normalize_range($ipOrCIDR) {
# 2001:1800::1/21
# --> 2001:1800::/21
 
# 2001:1af8:4900:a012:0002::1337
# --> 2001:1af8:4900:a012:2::1337/128
 
$ary = ipv6_cidr_split($ipOrCIDR);
$ipOrCIDR = $ary[0];
$cidr_bits = $ary[1];
if ($cidr_bits > IPV6_BITS) return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
if (!is_numeric($cidr_bits)) return false;
 
$m = ip2bin($ipOrCIDR);
$m = substr($m, 0, $cidr_bits) . str_repeat('0', IPV6_BITS-$cidr_bits);
 
return bin2ip($m) . '/' . $cidr_bits;
}
 
function ipv6_trackdown($ipOrCIDR) {
$ary = ipv6_cidr_split($ipOrCIDR);
$ipOrCIDR = $ary[0];
$cidr_bits = $ary[1];
if ($cidr_bits > IPV6_BITS) return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
if (!is_numeric($cidr_bits)) return false;
 
$out = array();
$m = ip2bin($ipOrCIDR);
for ($i=$cidr_bits; $i>=0; $i--) {
$m = substr($m, 0, $i) . str_repeat('0', IPV6_BITS-$i);
$out[] = bin2ip($m) . '/' . $i;
}
 
return $out;
}
 
function ipv6_sort($ary) {
$f = array();
foreach ($ary as $c) {
$a = explode('/', $c);
$ip = $a[0];
$bits = isset($a[1]) ? $a[1] : 128;
 
$d = ip2bin($ip);
 
# ord('*') must be smaller than ord('0')
$d = substr($d, 0, $bits).str_repeat('*', 128-$bits);
 
$f[$d] = $c;
}
 
return $f;
}
 
function ipv6_make_tree($ary) {
$ary = ipv6_sort($ary);
 
if (count($ary) == 0) return array();
 
$sub_begin = '';
$sub_begin_ip = '';
foreach ($ary as $n => $d) {
$sub_begin = substr($n, 0, strpos($n, '*'));
$sub_begin_ip = $d;
unset($ary[$n]);
break;
}
 
$sub = array();
$nonsub = array();
foreach ($ary as $n => $d) {
if (substr($n, 0, strlen($sub_begin)) == $sub_begin) {
$sub[$n] = $d;
} else {
$nonsub[$n] = $d;
}
}
 
$out = array();
$out[$sub_begin_ip] = ipv6_make_tree($sub);
 
$a = ipv6_make_tree($nonsub);
 
$out = array_merge($out, $a);
 
return $out;
}
 
# ---
 
if (!function_exists('ip2bin')) {
$cache_ip2bin = array();
function ip2bin($ip) {
# Source: http://php.net/manual/en/function.ip2long.php#104163
# modified by VTS
 
global $cache_ip2bin;
if (isset($cache_ip2bin[$ip])) return $cache_ip2bin[$ip];
 
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) {
$iplong = ip2long($ip);
assert($iplong !== false);
$out = base_convert((string)$iplong, 10, 2);
$cache_ip2bin[$ip] = $out;
return $out;
}
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
$cache_ip2bin[$ip] = false;
return false;
}
if (($ip_n = inet_pton($ip)) === false) {
$cache_ip2bin[$ip] = false;
return false;
}
$bits = 15; // 16 x 8 bit = 128bit (ipv6)
$ipbin = ''; # added by vts to avoid warning
while ($bits >= 0) {
$bin = sprintf('%08b', (ord($ip_n[$bits])));
$ipbin = $bin.$ipbin;
$bits--;
}
 
$cache_ip2bin[$ip] = $ipbin;
return $ipbin;
}
}
 
if (!function_exists('bin2ip')) {
$cache_bin2ip = array();
function bin2ip($bin) {
# Source: http://php.net/manual/en/function.ip2long.php#104163
# modified by VTS
 
global $cache_bin2ip;
if (isset($cache_bin2ip[$bin])) return $cache_bin2ip[$bin];
 
if (strlen($bin) <= 32) { // 32bits (ipv4)
$iplong = base_convert($bin, 2, 10);
$out = long2ip(intval($iplong));
$cache_bin2ip[$bin] = $out;
return $out;
}
if (strlen($bin) != 128) {
$cache_bin2ip[$bin] = false;
return false;
}
//$bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
$bits = 0;
$ipv6 = ''; # added by vts to avoid warning
while ($bits <= 7) {
$bin_part = substr($bin,($bits*16),16);
$ipv6 .= dechex(bindec($bin_part)) . ':';
$bits++;
}
 
$out = inet_ntop(inet_pton(substr($ipv6, 0, -1)));
$cache_bin2ip[$bin] = $out;
return $out;
}
}
 
# --- TEST
 
/*
assert(ipv6_normalize('2001:0000:0000::1') == '2001::1');
 
assert(ipv6_distance('2001:1ae0::/27', '2001:1af8::/29') == -2);
assert(ipv6_distance('2001:1af0::/28', '2001:1af8::/29') == -1);
assert(ipv6_distance('2001:1af8::/29', '2001:1af8::/29') == 0);
assert(ipv6_distance('2001:1af8::/30', '2001:1af8::/29') == 1);
assert(ipv6_distance('2001:1af8::/31', '2001:1af8::/29') == 2);
 
assert(ipv6_distance('2001:1af8:4900:a012:0002::1336/127', '2001:1af8:4900:a012:0002::1335/127') === false);
assert(ipv6_distance('2001:1af8:4900:a012:0002::1336/128', '2001:1af8:4900:a012:0002::1337/128') === false);
assert(ipv6_distance('2001:1af8:4900:a012:0002::1336', '2001:1af8:4900:a012:0002::1337') === false);
*/
 
/*
$test = '2001:1af8:4900:a012:0002::1337';
$x = ipv6_trackdown($test);
foreach ($x as &$cidr) {
$min = ipv6_cidr_min_ip($cidr);
$max = ipv6_cidr_max_ip($cidr);
echo "$cidr ($min - $max)\n";
}
*/
/trunk/last_weekday_date.inc.php
0,0 → 1,29
<?php
 
/*
* last_weekday_date.inc.php
* Copyright 2019 Daniel Marschall, ViaThinkSoft
* Version 2019-04-29
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
function get_last_weekday_date($dow) {
for ($i=0; $i<=6; $i++) {
$d = time()-$i*86400;
$e = date('N', $d);
if ($e == $dow) {
return date('d.m.Y', $d);
}
}
}
/trunk/misc_functions.inc.php
0,0 → 1,228
<?php
 
/*
* PHP Utilities - Misc functions
* Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
* Revision: 2023-08-01
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
// array(89,51,10,10) => 'Y3\012\012'
function c_literal($byte_array) {
$out = "'";
foreach ($byte_array as $c) {
if (is_string($c)) $c = ord($c);
if ((($c >= 0x00) && ($c <= 0x1F)) || ($c >= 0x7F)) {
// For non-printable characters use octal notation:
// \000 ... \377
$out .= "\\".str_pad(base_convert(''.$c,10,8), 3, '0', STR_PAD_LEFT);
} else {
if (chr($c) == "'") $out .= '\\';
$out .= chr($c);
}
}
$out .= "'";
return $out;
}
 
function c_literal_hexstr($hexstr) {
$odd_char = (strlen($hexstr)%2 != 0) ? '0x'.substr($hexstr,-1).'<<4' : '';
$hexstr = substr($hexstr,0,2*(int)floor(strlen($hexstr)/2));
if ($hexstr != '') {
$ary = str_split(hex2bin($hexstr));
foreach ($ary as &$a) $a = ord($a);
return rtrim(c_literal($ary).' '.$odd_char);
} else {
return $odd_char;
}
}
 
function generateRandomString($length) {
// Note: This function can be used in temporary file names, so you
// may not generate illegal file name characters.
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
 
function trim_br($html) {
$count = 0;
do { $html = preg_replace('@^\s*<\s*br\s*/{0,1}\s*>@isU', '', $html, -1, $count); } while ($count > 0); // left trim
do { $html = preg_replace('@<\s*br\s*/{0,1}\s*>\s*$@isU', '', $html, -1, $count); } while ($count > 0); // right trim
return $html;
}
 
function insertWhitespace($str, $index) {
return substr($str, 0, $index) . ' ' . substr($str, $index);
}
 
function js_escape($data) {
// TODO.... json_encode??
$data = str_replace('\\', '\\\\', $data);
$data = str_replace('\'', '\\\'', $data);
return "'" . $data . "'";
}
 
function get_calling_function() {
$ex = new Exception();
$trace = $ex->getTrace();
if (!isset($trace[2])) return '(main)';
$final_call = $trace[2];
return $final_call['file'].':'.$final_call['line'].'/'.$final_call['function'].'()';
}
 
function convert_to_utf8_no_bom($cont) {
$cont = vts_utf8_encode($cont);
 
// Remove BOM
$bom = pack('H*','EFBBBF');
$cont = preg_replace("/^$bom/", '', $cont);
return $cont;
}
 
function vts_utf8_encode($text) {
$enc = mb_detect_encoding($text, null, true);
if ($enc === false) $enc = mb_detect_encoding($text, ['ASCII', 'UTF-8', 'Windows-1252', 'ISO-8859-1'], true);
if ($enc === false) $enc = null;
 
if ($enc === 'UTF-8') return $text;
 
$res = mb_convert_encoding($text, 'UTF-8', $enc);
if ($res === false) $res = iconv('UTF-8', 'UTF-8//IGNORE', $text);
 
return $res;
}
 
function vts_utf8_decode($text) {
$enc = mb_detect_encoding($text, null, true);
if ($enc === false) $enc = mb_detect_encoding($text, ['ASCII', 'UTF-8', 'Windows-1252', 'ISO-8859-1'], true);
if ($enc === false) $enc = null;
 
if ($enc !== 'UTF-8') return $text;
 
$res = mb_convert_encoding($text, 'Windows-1252', $enc);
if ($res === false) $res = iconv('Windows-1252', 'Windows-1252//IGNORE', $text);
 
return $res;
}
 
function stripHtmlComments($html) {
// https://stackoverflow.com/questions/11337332/how-to-remove-html-comments-in-php
$html = preg_replace("~<!--(?!<!)[^\[>].*?-->~s", "", $html);
return $html;
}
 
function wildcard_is_dir($dir) {
// Example usage: if (!wildcard_is_dir(OIDplus::localpath().'plugins/'.'*'.'/design/'.$value)) throw new Exception("Design does not exist")
$dirs = @glob($dir);
if ($dirs) foreach ($dirs as $dir) {
if (is_dir($dir)) return true;
}
return false;
}
 
function isInternetExplorer() {
$ua = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
return ((strpos($ua,'MSIE ') !== false) || (strpos($ua,'Trident/') !== false));
}
 
if (!function_exists('str_starts_with')) {
// PHP 7.x compatibility
// source: Laravel Framework
// https://github.com/laravel/framework/blob/8.x/src/Illuminate/Support/Str.php
function str_starts_with($haystack, $needle) {
return (string)$needle !== '' && strncmp($haystack, $needle, strlen($needle)) === 0;
}
}
 
if (!function_exists('str_ends_with')) {
// PHP 7.x compatibility
// source: Laravel Framework
// https://github.com/laravel/framework/blob/8.x/src/Illuminate/Support/Str.php
function str_ends_with($haystack, $needle) {
return $needle !== '' && substr($haystack, -strlen($needle)) === (string)$needle;
}
}
 
if (!function_exists('str_contains')) {
// PHP 7.x compatibility
// source: Laravel Framework
// https://github.com/laravel/framework/blob/8.x/src/Illuminate/Support/Str.php
function str_contains($haystack, $needle) {
return $needle !== '' && mb_strpos($haystack, $needle) !== false;
}
}
 
function random_bytes_ex($len, $raw=true, $force_cryptographically_secure=true) {
if ($len === 0) return '';
assert($len > 0);
 
if (function_exists('random_bytes')) {
try {
$a = random_bytes($len);
} catch (Exception $e) { $a = null; }
if ($a) return $raw ? $a : bin2hex($a);
}
 
if (function_exists('openssl_random_pseudo_bytes')) {
try {
$a = openssl_random_pseudo_bytes($len);
} catch (Exception $e) { $a = null; }
if ($a) return $raw ? $a : bin2hex($a);
}
 
if (function_exists('mcrypt_create_iv') && defined('MCRYPT_DEV_RANDOM')) {
try {
$a = bin2hex(mcrypt_create_iv($len, MCRYPT_DEV_RANDOM));
} catch (Exception $e) { $a = null; }
if ($a) return $raw ? $a : bin2hex($a);
}
 
if ($force_cryptographically_secure) {
$msg = 'Cannot find a fitting Cryptographically Secure Random Number Generator (CSRNG).';
if (version_compare(PHP_VERSION, '8.2.0') >= 0) {
throw new \Random\RandomException($msg);
} else {
throw new \Exception($msg);
}
}
 
if (function_exists('mcrypt_create_iv') && defined('MCRYPT_DEV_URANDOM')) {
// /dev/urandom uses the same entropy pool than /dev/random, but if there is not enough data
// then the security is lowered.
try {
$a = bin2hex(mcrypt_create_iv($len, MCRYPT_DEV_URANDOM));
} catch (Exception $e) { $a = null; }
if ($a) return $raw ? $a : bin2hex($a);
}
 
if (function_exists('mcrypt_create_iv') && defined('MCRYPT_RAND')) {
try {
$a = bin2hex(mcrypt_create_iv($len, MCRYPT_RAND));
} catch (Exception $e) { $a = null; }
if ($a) return $raw ? $a : bin2hex($a);
}
 
// Fallback to non-secure RNG
$a = '';
while (strlen($a) < $len*2) {
$a .= sha1(uniqid((string)mt_rand(), true));
}
$a = substr($a, 0, $len*2);
return $raw ? hex2bin($a) : $a;
}
/trunk/oid_utils.inc.php
0,0 → 1,916
<?php
 
/*
* OID-Utilities for PHP
* Copyright 2011 - 2023 Daniel Marschall, ViaThinkSoft
* Version 2023-08-25
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
// All functions in this library are compatible with leading zeroes (not recommended) and leading dots
 
// TODO: change some function names, so that they have a uniform naming schema, and rename "oid identifier" into "ASN.1 alphanumeric identifier"
// oid_id_is_valid() => asn1_alpha_id_valid()
 
define('OID_DOT_FORBIDDEN', 0);
define('OID_DOT_OPTIONAL', 1);
define('OID_DOT_REQUIRED', 2);
 
/**
* Checks if an OID has a valid dot notation.
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-09
* @param string $oid<br/>
* An OID in dot notation.
* @param boolean $allow_leading_zeroes<br/>
* true of leading zeroes are allowed or not.
* @param boolean $allow_leading_dot<br/>
* true of leading dots are allowed or not.
* @return boolean true if the dot notation is valid.
**/
function oid_valid_dotnotation($oid, $allow_leading_zeroes=true, $allow_leading_dot=false, $min_len=0) {
$regex = oid_validation_regex($allow_leading_zeroes, $allow_leading_dot, $min_len);
 
$m = array();
return preg_match($regex, $oid, $m) ? true : false;
}
 
/**
* Returns a full regular expression to validate an OID in dot-notation
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-09
* @param boolean $allow_leading_zeroes<br/>
* true of leading zeroes are allowed or not.
* @param boolean $allow_leading_dot<br/>
* true of leading dots are allowed or not.
* @return string The regular expression
**/
function oid_validation_regex($allow_leading_zeroes=true, $allow_leading_dot=false, $min_len=0) {
$leading_dot_policy = $allow_leading_dot ? OID_DOT_OPTIONAL : OID_DOT_FORBIDDEN;
 
$part_regex = oid_part_regex($min_len, $allow_leading_zeroes, $leading_dot_policy);
 
return '@^'.$part_regex.'$@';
}
 
/**
* Returns a partial regular expression which matches valid OIDs in dot notation.
* It can be inserted into regular expressions.
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-09
* @param int $min_len<br/>
* 0="." and greater will be recognized, but not ""<br/>
* 1=".2" and greater will be recognized<br/>
* 2=".2.999" and greater will be recognized (default)<br/>
* etc.
* @param boolean $allow_leading_zeroes<br/>
* true: ".2.0999" will be recognized<br/>
* false: ".2.0999" won't be recognized (default)
* @param int $leading_dot_policy<br/>
* 0 (OID_DOT_FORBIDDEN): forbidden<br/>
* 1 (OID_DOT_OPTIONAL) : optional (default)<br/>
* 2 (OID_DOT_REQUIRED) : enforced
* @return string|false A regular expression which matches OIDs in dot notation
**/
function oid_part_regex($min_len=2, $allow_leading_zeroes=false, $leading_dot_policy=OID_DOT_OPTIONAL) {
switch ($leading_dot_policy) {
case 0: // forbidden
$lead_dot = '';
break;
case 1: // optional
$lead_dot = '\\.{0,1}';
break;
case 2: // enforced
$lead_dot = '\\.';
break;
default:
assert(false);
return false;
}
 
$lead_zero = $allow_leading_zeroes ? '0*' : '';
$zero_till_thirtynine = '(([0-9])|([1-3][0-9]))'; // second arc is limited to 0..39 if root arc is 0..1
$singledot_option = ($min_len == 0) && ($leading_dot_policy != OID_DOT_FORBIDDEN) ? '|\\.' : '';
$only_root_option = ($min_len <= 1) ? '|('.$lead_dot.$lead_zero.'[0-2])' : '';
 
$regex = '
(
(
(
('.$lead_dot.$lead_zero.'[0-1])
\\.'.$lead_zero.$zero_till_thirtynine.'
(\\.'.$lead_zero.'(0|[1-9][0-9]*)){'.max(0, $min_len-2).',}
)|(
('.$lead_dot.$lead_zero.'[2])
(\\.'.$lead_zero.'(0|[1-9][0-9]*)){'.max(0, $min_len-1).',}
)
'.$only_root_option.'
'.$singledot_option.'
)
)';
 
// Remove the indentations which are used to maintain this large regular expression in a human friendly way
$regex = str_replace("\n", '', $regex);
$regex = str_replace("\r", '', $regex);
$regex = str_replace("\t", '', $regex);
$regex = str_replace(' ', '', $regex);
 
return $regex;
}
 
/**
* Searches all OIDs in $text and outputs them as array.
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-09
* @param string $text<br/>
* The text to be parsed
* @param int $min_len<br/>
* 0="." and greater will be recognized, but not ""<br/>
* 1=".2" and greater will be recognized<br/>
* 2=".2.999" and greater will be recognized (default)<br/>
* etc.
* @param boolean $allow_leading_zeroes<br/>
* true: ".2.0999" will be recognized<br/>
* false: ".2.0999" won't be recognized (default)
* @param int $leading_dot_policy<br/>
* 0 (OID_DOT_FORBIDDEN): forbidden<br/>
* 1 (OID_DOT_OPTIONAL) : optional (default)<br/>
* 2 (OID_DOT_REQUIRED) : enforced
* @param boolean $requires_whitespace_delimiters<br/>
* true: "2.999" will be recognized, as well as " 2.999 " (default)<br/>
* false: "2.999!" will be reconigzed, as well as "2.999.c" (this might be used in in documentations with templates)
* @return string[] An array of OIDs in dot notation
**/
function parse_oids($text, $min_len=2, $allow_leading_zeroes=false, $leading_dot_policy=OID_DOT_OPTIONAL, $requires_whitespace_delimiters=true) {
$regex = oid_detection_regex($min_len, $allow_leading_zeroes, $leading_dot_policy, $requires_whitespace_delimiters);
 
$matches = array();
preg_match_all($regex, $text, $matches);
return $matches[1];
}
 
/**
* Returns a full regular expression for detecting OIDs in dot notation inside a text.
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-09
* @param int $min_len<br/>
* 0="." and greater will be recognized, but not ""<br/>
* 1=".2" and greater will be recognized<br/>
* 2=".2.999" and greater will be recognized (default)<br/>
* etc.
* @param boolean $allow_leading_zeroes<br/>
* true: ".2.0999" will be recognized<br/>
* false: ".2.0999" won't be recognized (default)
* @param int $leading_dot_policy<br/>
* 0 (OID_DOT_FORBIDDEN): forbidden<br/>
* 1 (OID_DOT_OPTIONAL) : optional (default)<br/>
* 2 (OID_DOT_REQUIRED) : enforced
* @param boolean $requires_whitespace_delimiters<br/>
* true: "2.999" will be recognized, as well as " 2.999 " (default)<br/>
* false: "2.999!" will be reconigzed, as well as "2.999.c" (this might be used in in documentations with templates)
* @return string The regular expression
**/
function oid_detection_regex($min_len=2, $allow_leading_zeroes=false, $leading_dot_policy=OID_DOT_OPTIONAL, $requires_whitespace_delimiters=true) {
if ($requires_whitespace_delimiters) {
// A fully qualified regular expression which can be used by preg_match()
$begin_condition = '(?<=^|\\s)';
$end_condition = '(?=\\s|$)';
} else {
// A partial expression which can be used inside another regular expression
$begin_condition = '(?<![\d])';
$end_condition = '(?![\d])';
}
 
$part_regex = oid_part_regex($min_len, $allow_leading_zeroes, $leading_dot_policy);
 
return '@'.$begin_condition.$part_regex.$end_condition.'@';
}
 
/**
* Returns the parent of an OID in dot notation or the OID itself, if it is the root.<br/>
* Leading dots and leading zeroes are tolerated.
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-16
* @param string $oid<br/>
* An OID in dot notation.
* @return string|false The parent OID in dot notation.
**/
function oid_up($oid) {
$oid = sanitizeOID($oid, 'auto');
if ($oid === false) return false;
 
$p = strrpos($oid, '.');
if ($p === false) return $oid;
if ($p == 0) return '.';
 
return substr($oid, 0, $p);
}
 
/**
* Outputs the depth of an OID.
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-09
* @param string $oid An OID in dot notation (with or without leading dot)
* @return int The depth of the OID, e.g. 2.999 and .2.999 has the length 2.
**/
function oid_len($oid) {
if ($oid == '') return 0;
if (substr($oid,0,1) == '.') $oid = substr($oid, 1);
return substr_count($oid, '.')+1;
}
function oid_depth($oid) {
return oid_len($oid);
}
 
/**
* Lists all parents of an OID.
* This function tolerates leading dots. The parent of '.' stays '.'.
* The OID will not be checked for validity!
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-17
* @param string $oid<br/>
* An OID in dot notation.
* @return string[] An array with all parent OIDs.
**/
function oid_parents($oid) {
$parents = array();
 
while (oid_len($oid) > 1) {
$oid = oid_up($oid);
$parents[] = $oid;
}
 
if (substr($oid, 0, 1) == '.') $parents[] = '.';
 
return $parents;
}
 
/*
assert(oid_parents('.1.2.999') == array('.1.2', '.1', '.'));
assert(oid_parents('1.2.999') == array('1.2', '1'));
assert(oid_parents('.') == array('.'));
assert(oid_parents('') == array());
*/
 
/**
* Sorts an array containing OIDs in dot notation.
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-09
* @param string[] $ary<br/>
* An array of OIDs in dot notation.<br/>
* This array will be changed by this method.
* @param boolean $output_with_leading_dot<br/>
* true: The array will be normalized to OIDs with a leading dot.
* false: The array will be normalized to OIDs without a leading dot. (default)
**/
function oidSort(&$ary, $output_with_leading_dot=false) {
$out = array();
 
$none = $output_with_leading_dot ? '.' : '';
 
$d = array();
$oid = null;
foreach ($ary as &$oid) {
if (($oid == '') || ($oid == '.')) {
$out[] = $none;
} else {
$oid = sanitizeOID($oid, 'auto'); // strike leading zeroes
$bry = explode('.', $oid, 2);
$firstarc = $bry[0];
$rest = (isset($bry[1])) ? $bry[1] : '';
$d[$firstarc][] = $rest;
}
}
unset($oid);
ksort($d);
 
$data = null;
foreach ($d as $firstarc => &$data) {
oidSort($data);
foreach ($data as &$rest) {
$out[] = ($output_with_leading_dot ? '.' : '')."$firstarc" . (($rest != $none) ? ".$rest" : '');
}
}
unset($data);
 
$ary = $out;
}
 
/**
* Checks if two OIDs in dot-notation are equal
* @author Daniel Marschall, ViaThinkSoft
* @version 2020-05-27
* @param string $oidA<br/>
* First OID
* @param string $oidB<br/>
* Second OID
* @return boolean|null True if the OIDs are equal, null if one of the OIDs are invalid
**/
function oid_dotnotation_equal($oidA, $oidB) {
$oidA = sanitizeOID($oidA, false);
if ($oidA === false) return null;
 
$oidB = sanitizeOID($oidB, false);
if ($oidB === false) return null;
 
return $oidA === $oidB;
}
 
/**
* Removes leading zeroes from an OID in dot notation.
* @author Daniel Marschall, ViaThinkSoft
* @version 2015-08-17
* @param string $oid<br/>
* An OID in dot notation.
* @param mixed $leading_dot<br/>
* true: The OID is valid, if it contains a leading dot.<br/>
* false (default): The OID is valid, if it does not contain a leading dot.
* 'auto: Allow both
* @return string|false The OID without leading dots, or <code>false</code> if the OID is syntactically wrong.
**/
function sanitizeOID($oid, $leading_dot=false) {
static $oid_sanitize_cache = array();
 
if ($leading_dot) $leading_dot = substr($oid,0,1) == '.';
 
// We are using a cache, since this function is used very often by OIDplus
global $oid_sanitize_cache;
$v = ($leading_dot ? 'T' : 'F').$oid;
if (isset($oid_sanitize_cache[$v])) return $oid_sanitize_cache[$v];
 
if ($leading_dot) {
if ($oid == '.') return '';
} else {
if ($oid == '') return '';
}
 
$out = '';
$ary = explode('.', $oid);
foreach ($ary as $n => &$a) {
if (($leading_dot) && ($n == 0)) {
if ($a != '') return false;
continue;
}
 
if (!preg_match('@^(\\d+)$@', $a, $m)) return false; // does contain something other than digits
 
// strike leading zeroes
$a = preg_replace("@^0+@", '', $a);
if ($a == '') $a = 0;
 
if (($leading_dot) || ($n != 0)) $out .= '.';
$out .= $a;
}
unset($a);
unset($ary);
 
$oid_sanitize_cache[$v] = $out;
return $out;
}
 
/**
* Shows the top arc of an OID.
* This function tolerates leading dots.
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-16
* @param string $oid<br/>
* An OID in dot notation.
* @return string|false The top arc of the OID or empty string if it is already the root ('.')
**/
function oid_toparc($oid) {
$leadingdot = substr($oid,0,1) == '.';
 
$oid = sanitizeOID($oid, $leadingdot);
if ($oid === false) return false;
 
if (!$leadingdot) $oid = '.'.$oid;
 
$p = strrpos($oid, '.');
if ($p === false) return false;
$r = substr($oid, $p+1);
 
if ($leadingdot) {
# if ($r == '') return '.';
return $r;
} else {
return substr($r, 1);
}
}
 
/**
* Calculates the distance between two OIDs.
* This function tolerates leading dots and leading zeroes.
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-20
* @param string $a<br/>
* An OID.
* @param string $b<br/>
* An OID.
* @return int|false false if both OIDs do not have a child-parent or parent-child relation, e.g. oid_distance('2.999.1.2.3', '2.999.4.5') = false, or if one of the OIDs is syntactially invalid<br/>
* >0 if $a is more specific than $b , e.g. oid_distance('2.999.1.2', '2.999') = 2<br/>
* <0 if $a is more common than $b , e.g. oid_distance('2.999', '2.999.1.2') = -2
**/
function oid_distance($a, $b) {
if (substr($a,0,1) == '.') $a = substr($a,1);
if (substr($b,0,1) == '.') $b = substr($b,1);
 
$a = sanitizeOID($a, false);
if ($a === false) return false;
$b = sanitizeOID($b, false);
if ($b === false) return false;
 
$ary = explode('.', $a);
$bry = explode('.', $b);
 
$min_len = min(count($ary), count($bry));
 
for ($i=0; $i<$min_len; $i++) {
if ($ary[$i] != $bry[$i]) return false;
}
 
return count($ary) - count($bry);
}
/*
assert(oid_distance('2.999.1.2.3', '2.999.4.5') === false);
assert(oid_distance('2.999.1.2', '2.999') === 2);
assert(oid_distance('2.999', '2.999.1.2') === -2);
*/
 
/**
* Adds a leading dot to an OID.
* Leading zeroes are tolerated.
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-20
* @param string $oid<br/>
* An OID.
* @return string|false The OID with a leading dot or false if the OID is syntactially wrong.
**/
function oid_add_leading_dot($oid) {
$oid = sanitizeOID($oid, 'auto');
if ($oid === false) return false;
 
if (substr($oid,0,1) != '.') $oid = '.'.$oid;
return $oid;
}
 
/**
* Removes a leading dot to an OID.
* Leading zeroes are tolerated.
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-20
* @param string $oid<br/>
* An OID.
* @return string|false The OID without a leading dot or false if the OID is syntactially wrong.
**/
function oid_remove_leading_dot($oid) {
$oid = sanitizeOID($oid, 'auto');
if ($oid === false) return false;
 
if (substr($oid,0,1) == '.') $oid = substr($oid, 1);
return $oid;
}
 
/**
* Find the common ancestor of two or more OIDs
* @author Daniel Marschall, ViaThinkSoft
* @version 2020-05-27
* @param string[] $oids<br/>
* An array of multiple OIDs, e.g. 2.999.1 and 2.999.2.3.4
* @return string|false The common ancestor, e.g. 2.999, or false if there is no common ancestor.
**/
function oid_common_ancestor(array $oids) {
$shared = array();
 
if (!is_array($oids)) return false;
if (count($oids) === 0) return false;
 
foreach ($oids as &$oid) {
$oid = sanitizeOID($oid, false);
if ($oid === false) return false;
$oid = explode('.', $oid);
}
 
$max_ok = strlen($oids[0]);
for ($i=1; $i<count($oids); $i++) {
for ($j=0; $j<min(strlen($oids[$i]),strlen($oids[0])); $j++) {
if ($oids[$i][$j] != $oids[0][$j]) {
if ($j < $max_ok) $max_ok = $j;
break;
}
}
if ($j < $max_ok) $max_ok = $j;
}
 
$out = array();
for ($i=0; $i<$max_ok; $i++) {
$out[] = $oids[0][$i];
}
return implode('.', $out);
}
/*
assert(oid_shared_ancestor(array('2.999.4.5.3', '2.999.4.5')) === "2.999.4.5");
assert(oid_shared_ancestor(array('2.999.4.5', '2.999.4.5.3')) === "2.999.4.5");
assert(oid_shared_ancestor(array('2.999.1.2.3', '2.999.4.5')) === "2.999");
*/
 
 
# === OID-IRI NOTATION FUNCTIONS ===
 
if (!function_exists('mb_ord')) {
# http://stackoverflow.com/a/24755772/3544341
function mb_ord($char, $encoding = 'UTF-8') {
if ($encoding === 'UCS-4BE') {
list(, $ord) = (strlen($char) === 4) ? @unpack('N', $char) : @unpack('n', $char);
return $ord;
} else {
return mb_ord(mb_convert_encoding($char, 'UCS-4BE', $encoding), 'UCS-4BE');
}
}
}
 
function iri_char_valid($c, $firstchar, $lastchar) {
// see Rec. ITU-T X.660, clause 7.5
 
if (($firstchar || $lastchar) && ($c == '-')) return false;
 
if ($c == '-') return true;
if ($c == '.') return true;
if ($c == '_') return true;
if ($c == '~') return true;
if (($c >= '0') && ($c <= '9') && (!$firstchar)) return true;
if (($c >= 'A') && ($c <= 'Z')) return true;
if (($c >= 'a') && ($c <= 'z')) return true;
 
$v = mb_ord($c);
 
if (($v >= 0x000000A0) && ($v <= 0x0000DFFE)) return true;
if (($v >= 0x0000F900) && ($v <= 0x0000FDCF)) return true;
if (($v >= 0x0000FDF0) && ($v <= 0x0000FFEF)) return true;
if (($v >= 0x00010000) && ($v <= 0x0001FFFD)) return true;
if (($v >= 0x00020000) && ($v <= 0x0002FFFD)) return true;
if (($v >= 0x00030000) && ($v <= 0x0003FFFD)) return true;
if (($v >= 0x00040000) && ($v <= 0x0004FFFD)) return true;
if (($v >= 0x00050000) && ($v <= 0x0005FFFD)) return true;
if (($v >= 0x00060000) && ($v <= 0x0006FFFD)) return true;
if (($v >= 0x00070000) && ($v <= 0x0007FFFD)) return true;
if (($v >= 0x00080000) && ($v <= 0x0008FFFD)) return true;
if (($v >= 0x00090000) && ($v <= 0x0009FFFD)) return true;
if (($v >= 0x000A0000) && ($v <= 0x000AFFFD)) return true;
if (($v >= 0x000B0000) && ($v <= 0x000BFFFD)) return true;
if (($v >= 0x000C0000) && ($v <= 0x000CFFFD)) return true;
if (($v >= 0x000D0000) && ($v <= 0x000DFFFD)) return true;
if (($v >= 0x000E1000) && ($v <= 0x000EFFFD)) return true;
 
// Note: Rec. ITU-T X.660, clause 7.5.3 would also forbid ranges which are marked in ISO/IEC 10646 as "(This position shall not be used)"
// But tool implementers should be tolerate them, since these limitations can be removed in future.
 
return false;
}
 
function iri_arc_valid($arc, $allow_numeric=true) {
if ($arc == '') return false;
 
$m = array();
if ($allow_numeric && preg_match('@^(\\d+)$@', $arc, $m)) return true; # numeric arc
 
// Question: Should we strip RTL/LTR characters?
 
if (mb_substr($arc, 2, 2) == '--') return false; // see Rec. ITU-T X.660, clause 7.5.4
 
$array = array();
preg_match_all('/./u', $arc, $array, PREG_SET_ORDER);
$len = count($array);
foreach ($array as $i => $char) {
if (!iri_char_valid($char[0], $i==0, $i==$len-1)) return false;
}
 
return true;
}
 
/**
* Checks if an IRI identifier is valid or not.
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-17
* @param string $iri<br/>
* An OID in OID-IRI notation, e.g. /Example/test
* @return boolean true if the IRI identifier is valid.
**/
function iri_valid($iri) {
if ($iri == '/') return true; // OK?
 
if (substr($iri, 0, 1) != '/') return false;
 
$ary = explode('/', $iri);
array_shift($ary);
foreach ($ary as $a) {
if (!iri_arc_valid($a)) return false;
}
 
return true;
}
 
/*
assert(iri_arc_valid('ABCDEF'));
assert(!iri_arc_valid('-ABCDEF'));
assert(!iri_arc_valid('ABCDEF-'));
assert(!iri_arc_valid(' ABCDEF'));
assert(!iri_arc_valid('2 ABCDEF'));
assert(!iri_arc_valid(''));
 
assert(!iri_valid(''));
assert(iri_valid('/'));
assert(iri_valid('/hello/world'));
assert(iri_valid('/123/world'));
assert(!iri_valid('/hello/0world'));
assert(!iri_valid('/hello/xo--test'));
assert(!iri_valid('/hello/-super-/sd'));
*/
 
/**
* Returns an associative array in the form 'ASN.1' => '/2/1' .
* @author Daniel Marschall, ViaThinkSoft
* @version 2018-01-05
* @see http://itu.int/go/X660
* @return array<string,string> An associative array in the form 'ASN.1' => '/2/1' .
**/
function iri_get_long_arcs() {
$iri_long_arcs = array();
$iri_long_arcs['ASN.1'] = '/2/1';
$iri_long_arcs['Country'] = '/2/16';
$iri_long_arcs['International-Organizations'] = '/2/23';
$iri_long_arcs['UUID'] = '/2/25';
$iri_long_arcs['Tag-Based'] = '/2/27';
$iri_long_arcs['BIP'] = '/2/41';
$iri_long_arcs['Telebiometrics'] = '/2/42';
$iri_long_arcs['Cybersecurity'] = '/2/48';
$iri_long_arcs['Alerting'] = '/2/49';
$iri_long_arcs['OIDResolutionSystem'] = '/2/50';
$iri_long_arcs['GS1'] = '/2/51';
$iri_long_arcs['Example'] = '/2/999'; // English
$iri_long_arcs['Exemple'] = '/2/999'; // French
$iri_long_arcs['Ejemplo'] = '/2/999'; // Spanish
$iri_long_arcs["\u{0627}\u{0644}\u{0645}\u{062B}\u{0627}\u{0644}"] = '/2/999'; // Arabic
$iri_long_arcs["\u{8303}\u{4F8B}"] = '/2/999'; // Chinese
$iri_long_arcs["\u{041F}\u{0440}\u{0438}\u{043C}\u{0435}\u{0440}"] = '/2/999'; // Russian
$iri_long_arcs["\u{C608}\u{C81C}"] = '/2/999'; // Korean
$iri_long_arcs["\u{4F8B}"] = '/2/999'; // Japanese
$iri_long_arcs['Beispiel'] = '/2/999'; // German
return $iri_long_arcs;
}
 
/**
* Tries to shorten/simplify an IRI by applying "long arcs", e.g. /2/999/123 -> /Example/123 .
* @author Daniel Marschall, ViaThinkSoft
* @version 2020-05-22
* @param string $iri<br/>
* An OID in OID-IRI notation, e.g. /Example/test
* @return string|false The modified IRI.
**/
function iri_add_longarcs($iri) {
$iri_long_arcs = iri_get_long_arcs();
 
if (!iri_valid($iri)) return false;
 
$ary = explode('/', $iri);
 
$ary_number_iri = $ary;
if ($ary_number_iri[1] == 'Joint-ISO-ITU-T') $ary_number_iri[1] = '2';
 
$number_iri = implode('/', $ary_number_iri);
 
foreach ($iri_long_arcs as $cur_longarc => $cur_iri) {
assert(iri_valid($cur_iri));
if (strpos($number_iri.'/', $cur_iri.'/') === 0) {
$cnt = substr_count($cur_iri, '/');
for ($i=1; $i<$cnt; $i++) {
array_shift($ary);
}
$ary[0] = '';
$ary[1] = $cur_longarc;
$iri = implode('/', $ary);
break;
}
}
 
return $iri;
}
/*
assert(iri_add_longarcs('/2/999/123') === '/Example/123');
*/
 
# === FUNCTIONS FOR OIDS IN ASN.1 NOTATION ===
 
/**
* Checks if an ASN.1 identifier is valid.
* @author Daniel Marschall, ViaThinkSoft
* @version 2020-05-22
* @param string $id<br/>
* An ASN.1 identifier, e.g. "example". Not "example(99)" or "99" and not a path like "{ 2 999 }"
* Note: Use asn1_path_valid() for validating a whole ASN.1 notation path.
* @return boolean true, if the identifier is valid: It begins with an lowercase letter and contains only 0-9, a-z, A-Z and "-"
**/
function oid_id_is_valid($id) {
// see Rec. ITU-T X.660 | ISO/IEC 9834-1, clause 7.7
// and Rec. ITU-T X.680 | ISO/IEC 8824-1, clause 12.3
if (substr($id,-1,1) == '-') return false;
if (strstr($id,'--')) return false;
return preg_match('/^([a-z][a-zA-Z0-9-]*)$/', $id) != 0;
}
 
/**
* Checks if the ASN.1 notation of an OID is valid.
* This function does not tolerate leading zeros.
* This function will fail (return false) if there are unresolved symbols, e.g. {iso test} is not valid while { iso 123 } is valid.
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-17
* @param string $asn1<br/>
* An OID in ASN.1 notation.
* @return boolean true if the identifier is valid.
**/
function asn1_path_valid($asn1) {
return asn1_to_dot($asn1) != false;
}
 
/**
* Returns an array of standardized ASN.1 alphanumeric identifiers which do not require a numeric identifier, e.g. { 2 example }
* The array has the form '0.0.a' -> '0.0.1'
* @author Daniel Marschall, ViaThinkSoft
* @version 2019-03-25
* @see http://www.oid-info.com/name-forms.htm
* @return array<string,string> Associative array of standardized ASN.1 alphanumeric identifiers
**/
function asn1_get_standardized_array() {
 
// Taken from oid-info.com
// http://www.oid-info.com/name-forms.htm
$standardized = array();
$standardized['itu-t'] = '0';
$standardized['ccitt'] = '0';
$standardized['iso'] = '1';
$standardized['joint-iso-itu-t'] = '2';
$standardized['joint-iso-ccitt'] = '2';
$standardized['0.recommendation'] = '0.0';
$standardized['0.0.a'] = '0.0.1';
$standardized['0.0.b'] = '0.0.2';
$standardized['0.0.c'] = '0.0.3';
$standardized['0.0.d'] = '0.0.4';
$standardized['0.0.e'] = '0.0.5';
$standardized['0.0.f'] = '0.0.6';
$standardized['0.0.g'] = '0.0.7';
$standardized['0.0.h'] = '0.0.8';
$standardized['0.0.i'] = '0.0.9';
$standardized['0.0.j'] = '0.0.10';
$standardized['0.0.k'] = '0.0.11';
$standardized['0.0.l'] = '0.0.12';
$standardized['0.0.m'] = '0.0.13';
$standardized['0.0.n'] = '0.0.14';
$standardized['0.0.o'] = '0.0.15';
$standardized['0.0.p'] = '0.0.16';
$standardized['0.0.q'] = '0.0.17';
$standardized['0.0.r'] = '0.0.18';
$standardized['0.0.s'] = '0.0.19';
$standardized['0.0.t'] = '0.0.20';
$standardized['0.0.u'] = '0.0.21';
$standardized['0.0.v'] = '0.0.22';
$standardized['0.0.w'] = '0.0.23'; // actually, this OID does not exist
$standardized['0.0.x'] = '0.0.24';
$standardized['0.0.y'] = '0.0.25';
$standardized['0.0.z'] = '0.0.26';
$standardized['0.question'] = '0.1';
$standardized['0.administration'] = '0.2';
$standardized['0.network-operator'] = '0.3';
$standardized['0.identified-organization'] = '0.4';
$standardized['1.standard'] = '1.0';
$standardized['1.registration-authority'] = '1.1';
$standardized['1.member-body'] = '1.2';
$standardized['1.identified-organization'] = '1.3';
return $standardized;
}
 
/**
* Converts an OID in ASN.1 notation into an OID in dot notation and tries to resolve well-known identifiers.<br/>
* e.g. {joint-iso-itu-t(2) example(999) 1 2 3} --> 2.999.1.2.3<br/>
* e.g. {iso 3} --> 1.3
* This function does not tolerate leading zeros.
* This function will fail (return false) if there are unresolved symbols, e.g. {iso test} will not be resolved to 1.test
* @author Daniel Marschall, ViaThinkSoft
* @version 2014-12-17
* @param string $asn<br/>
* An OID in ASN.1 notation.
* @return string|false An OID in dot notation without leading dot or false if the path is invalid.
**/
function asn1_to_dot($asn) {
$standardized = asn1_get_standardized_array();
 
// Clean up
$count = -1;
$asn = preg_replace('@^\\{(.+)\\}$@', '\\1', $asn, -1, $count);
if ($count == 0) return false; // { and } are required. The ASN.1 path will NOT be trimmed by this function
 
// If identifier is set, apply it (no check if it overrides a standardized identifier)
$asn = preg_replace('|\s*([a-z][a-zA-Z0-9-]*)\s*\((\d+)\)|', ' \\2', $asn);
$asn = trim($asn);
 
// Set dots
$asn = preg_replace('|\s+|', '.', $asn);
 
// Apply standardized identifiers (case sensitive)
$asn .= '.';
foreach ($standardized as $s => $r) {
$asn = preg_replace("@^".preg_quote($s,"@")."@", $r, $asn);
}
$asn = substr($asn, 0, strlen($asn)-1);
 
// Check if all numbers are OK
// -> every arc must be resolved
// -> numeric arcs must not have a leading zero
// -> invalid stuff will be recognized, e.g. a "(1)" without an identifier in front of it
$ary = explode('.', $asn);
foreach ($ary as $a) {
$m = array();
if (!preg_match('@^(0|([1-9]\\d*))$@', $a, $m)) return false;
}
 
return $asn;
}
 
/*
assert(asn1_to_dot('{2 999 (1)}') == false);
assert(asn1_to_dot('{2 999 test}') == false);
assert(asn1_to_dot('{2 999 1}') == '2.999.1');
assert(asn1_to_dot(' {2 999 1} ') == false);
assert(asn1_to_dot('2 999 1') == false);
assert(asn1_to_dot('{2 999 01}') == false);
assert(asn1_to_dot('{ 0 question 123 }') == '0.1.123');
assert(asn1_to_dot('{ iso }') == '1');
assert(asn1_to_dot('{ iso(1) }') == '1');
assert(asn1_to_dot('{ iso(2) }') == '2');
assert(asn1_to_dot('{ iso 3 }') == '1.3');
*/
 
/**
* Gets the last numeric identifier of an ASN.1 notation OID.
* @author Daniel Marschall, ViaThinkSoft
* @version 2020-06-11
* @param string $asn1id<br/>
* An ASN.1 identifier string, e.g. { 2 example(999) test(1) }
* @return int|false The last numeric identifier arc, e.g. "1" or false if the ID is invalid
**/
function asn1_last_identifier($asn1id) {
$asn1id = preg_replace('@\(\s*\d+\s*\)@', '', $asn1id);
$asn1id = trim(str_replace(array('{', '}', "\t"), ' ', $asn1id));
$ary = explode(' ', $asn1id);
$asn1id = $ary[count($ary)-1];
return preg_match('#[^0-9]#',$asn1id) ? (int)$asn1id : false;
}
 
/**
* "Soft corrects" an invalid ASN.1 identifier.<br/>
* Attention, by "soft correcting" the ID, it is not authoritative anymore, and might not be able to be resolved by ORS.
* @author Daniel Marschall, ViaThinkSoft
* @version 2020-05-22
* @param string $id<br/>
* An ASN.1 identifier.
* @param boolean $append_id_prefix<br/>
* true (default): If the identifier doesn't start with a-Z, the problem will be solved by prepending "id-" to the identifier.<br/>
* false: If the identifier doesn't start with a-Z, then the problem cannot be solved (method returns empty string).
* @return string The "soft corrected" ASN.1 identifier.<br/>
* Invalid characters will be removed.<br/>
* Uncorrectable start elements (0-9 or "-") will be either removed or solved by prepending "id-" (see <code>$append_id_prefix</code>)<br/>
* If the identifier begins with an upper case letter, the letter will be converted into lower case.
**/
function oid_soft_correct_id($id, $append_id_prefix = true) {
// Convert "_" to "-"
$id = str_replace('_', '-', $id);
 
// Convert "--" to "-"
$id = str_replace('--', '-', $id);
 
// Remove invalid characters
$id = preg_replace('/[^a-zA-Z0-9-]+/', '', $id);
 
// Remove uncorrectable start elements (0-9 or "-")
if ($append_id_prefix) {
$id = preg_replace('/^([^a-zA-Z]+)/', 'id-$1', $id);
} else {
$id = preg_replace('/^([^a-zA-Z]+)/', '', $id);
}
 
// "Correct" upper case beginning letter by converting it to lower case
if (preg_match('/^[A-Z]/', $id)) {
$id = strtolower($id[0]) . substr($id, 1);
}
 
return $id;
}
/trunk/openssl_supplement.inc.php
0,0 → 1,381
<?php
 
/*
* OpenSSL php functions implemented using phpseclib
* Copyright 2022 Daniel Marschall, ViaThinkSoft
* Version 2022-07-17
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
// How to use this supplement:
// 1. Include phpseclib using composer and include the autoloader
// 2. Then, include this file. The openssl functions are now available.
 
// ATTENTION: This supplement/polyfill does only implement a few openssl_*() functions,
// and only a few algorithms: AES and RSA! Feel free to extend this library!
// The sign/verify and encrypt/decrypt functions should be binary compatible with
// the actual openssl functions.
 
if (!function_exists('openssl_pkey_new') && class_exists('\\phpseclib3\\Crypt\\RSA')) {
 
define('OPENSSL_SUPPLEMENT', 1);
 
// ---------------------------------------------------------------------
 
// https://www.php.net/manual/en/openssl.purpose-check.php
if (!defined('X509_PURPOSE_SSL_CLIENT')) define('X509_PURPOSE_SSL_CLIENT', 1);
if (!defined('X509_PURPOSE_SSL_SERVER')) define('X509_PURPOSE_SSL_SERVER', 2);
if (!defined('X509_PURPOSE_NS_SSL_SERVER')) define('X509_PURPOSE_NS_SSL_SERVER', 3);
if (!defined('X509_PURPOSE_SMIME_SIGN')) define('X509_PURPOSE_SMIME_SIGN', 4);
if (!defined('X509_PURPOSE_SMIME_ENCRYPT')) define('X509_PURPOSE_SMIME_ENCRYPT', 5);
if (!defined('X509_PURPOSE_CRL_SIGN')) define('X509_PURPOSE_CRL_SIGN', 6);
if (!defined('X509_PURPOSE_ANY')) define('X509_PURPOSE_ANY', 7);
 
// https://www.php.net/manual/en/openssl.padding.php
if (!defined('OPENSSL_PKCS1_PADDING')) define('OPENSSL_PKCS1_PADDING', 1);
if (!defined('OPENSSL_SSLV23_PADDING')) define('OPENSSL_SSLV23_PADDING', 2);
if (!defined('OPENSSL_NO_PADDING')) define('OPENSSL_NO_PADDING', 3);
if (!defined('OPENSSL_PKCS1_OAEP_PADDING')) define('OPENSSL_PKCS1_OAEP_PADDING', 4);
 
// https://www.php.net/manual/en/openssl.key-types.php
if (!defined('OPENSSL_KEYTYPE_RSA')) define('OPENSSL_KEYTYPE_RSA', 0);
if (!defined('OPENSSL_KEYTYPE_DSA')) define('OPENSSL_KEYTYPE_DSA', 1);
if (!defined('OPENSSL_KEYTYPE_DH')) define('OPENSSL_KEYTYPE_DH', 2);
if (!defined('OPENSSL_KEYTYPE_EC')) define('OPENSSL_KEYTYPE_EC', 3);
 
// https://www.php.net/manual/en/openssl.pkcs7.flags.php
if (!defined('PKCS7_TEXT')) define('PKCS7_TEXT', 1);
if (!defined('PKCS7_BINARY')) define('PKCS7_BINARY', 128);
if (!defined('PKCS7_NOINTERN')) define('PKCS7_NOINTERN', 16);
if (!defined('PKCS7_NOVERIFY')) define('PKCS7_NOVERIFY', 32);
if (!defined('PKCS7_NOCHAIN')) define('PKCS7_NOCHAIN', 8);
if (!defined('PKCS7_NOCERTS')) define('PKCS7_NOCERTS', 2);
if (!defined('PKCS7_NOATTR')) define('PKCS7_NOATTR', 256);
if (!defined('PKCS7_DETACHED')) define('PKCS7_DETACHED', 64);
if (!defined('PKCS7_NOSIGS')) define('PKCS7_NOSIGS', 4);
 
// https://www.php.net/manual/en/openssl.cms.flags.php
if (!defined('OPENSSL_CMS_TEXT')) define('OPENSSL_CMS_TEXT', 1);
if (!defined('OPENSSL_CMS_BINARY')) define('OPENSSL_CMS_BINARY', 128);
if (!defined('OPENSSL_CMS_NOINTERN')) define('OPENSSL_CMS_NOINTERN', 16);
if (!defined('OPENSSL_CMS_NOVERIFY')) define('OPENSSL_CMS_NOVERIFY', 32);
if (!defined('OPENSSL_CMS_NOCERTS')) define('OPENSSL_CMS_NOCERTS', 2);
if (!defined('OPENSSL_CMS_NOATTR')) define('OPENSSL_CMS_NOATTR', 256);
if (!defined('OPENSSL_CMS_DETACHED')) define('OPENSSL_CMS_DETACHED', 64);
if (!defined('OPENSSL_CMS_NOSIGS')) define('OPENSSL_CMS_NOSIGS', 12);
 
// https://www.php.net/manual/en/openssl.signature-algos.php
if (!defined('OPENSSL_ALGO_DSS1')) define('OPENSSL_ALGO_DSS1', 5); // Only defined when php/openssl compiled with MD2 support
if (!defined('OPENSSL_ALGO_SHA1')) define('OPENSSL_ALGO_SHA1', 1);
if (!defined('OPENSSL_ALGO_SHA224')) define('OPENSSL_ALGO_SHA224', 6);
if (!defined('OPENSSL_ALGO_SHA256')) define('OPENSSL_ALGO_SHA256', 7);
if (!defined('OPENSSL_ALGO_SHA384')) define('OPENSSL_ALGO_SHA384', 8);
if (!defined('OPENSSL_ALGO_SHA512')) define('OPENSSL_ALGO_SHA512', 9);
if (!defined('OPENSSL_ALGO_RMD160')) define('OPENSSL_ALGO_RMD160', 10);
if (!defined('OPENSSL_ALGO_MD5')) define('OPENSSL_ALGO_MD5', 2);
if (!defined('OPENSSL_ALGO_MD4')) define('OPENSSL_ALGO_MD4', 3);
if (!defined('OPENSSL_ALGO_MD2')) define('OPENSSL_ALGO_MD2', 4); // Only defined when php/openssl compiled with MD2 support
 
// https://www.php.net/manual/en/openssl.ciphers.php
if (!defined('OPENSSL_CIPHER_RC2_40')) define('OPENSSL_CIPHER_RC2_40', 0);
if (!defined('OPENSSL_CIPHER_RC2_128')) define('OPENSSL_CIPHER_RC2_128', 1);
if (!defined('OPENSSL_CIPHER_RC2_64')) define('OPENSSL_CIPHER_RC2_64', 2);
if (!defined('OPENSSL_CIPHER_DES')) define('OPENSSL_CIPHER_DES', 3);
if (!defined('OPENSSL_CIPHER_3DES')) define('OPENSSL_CIPHER_3DES', 4);
if (!defined('OPENSSL_CIPHER_AES_128_CBC')) define('OPENSSL_CIPHER_AES_128_CBC', 5);
if (!defined('OPENSSL_CIPHER_AES_192_CBC')) define('OPENSSL_CIPHER_AES_192_CBC', 6);
if (!defined('OPENSSL_CIPHER_AES_256_CBC')) define('OPENSSL_CIPHER_AES_256_CBC', 7);
 
// https://www.php.net/manual/en/openssl.constversion.php
// OPENSSL_VERSION_TEXT (string)
// OPENSSL_VERSION_NUMBER (int)
 
// https://www.php.net/manual/en/openssl.constsni.php
// OPENSSL_TLSEXT_SERVER_NAME (string)
 
// https://www.php.net/manual/en/openssl.constants.other.php
if (!defined('OPENSSL_RAW_DATA')) define('OPENSSL_RAW_DATA', 1);
if (!defined('OPENSSL_ZERO_PADDING')) define('OPENSSL_ZERO_PADDING', 2);
if (!defined('OPENSSL_ENCODING_SMIME')) define('OPENSSL_ENCODING_SMIME', 1);
if (!defined('OPENSSL_ENCODING_DER')) define('OPENSSL_ENCODING_DER', 0);
if (!defined('OPENSSL_ENCODING_PEM')) define('OPENSSL_ENCODING_PEM', 2);
 
// ---------------------------------------------------------------------
 
$openssl_supplement_last_error = '';
 
function openssl_pkey_new($pkey_config=null) {
try {
$algo = $pkey_config && isset($pkey_config["private_key_type"]) ? $pkey_config["private_key_type"] : OPENSSL_KEYTYPE_RSA;
$bits = $pkey_config && isset($pkey_config["private_key_bits"]) ? $pkey_config["private_key_bits"] : 2048;
 
// TODO: Also support $pkey_config['encrypt_key'] and $pkey_config['encrypt_key_cipher'] ?
 
if ($algo == OPENSSL_KEYTYPE_RSA) {
$private = \phpseclib3\Crypt\RSA::createKey($bits);
} else {
throw new Exception("Algo not implemented");
}
 
$private = $private->withPadding(\phpseclib3\Crypt\RSA::ENCRYPTION_PKCS1 | \phpseclib3\Crypt\RSA::SIGNATURE_PKCS1);
 
$public = $private->getPublicKey()->withPadding(\phpseclib3\Crypt\RSA::ENCRYPTION_PKCS1 | \phpseclib3\Crypt\RSA::SIGNATURE_PKCS1);
 
return array($algo, $bits, $private, $public);
} catch (Exception $e) {
global $openssl_supplement_last_error;
$openssl_supplement_last_error = $e->getMessage();
return false;
}
}
 
function openssl_pkey_export($res, &$privKey, $passphrase = null, $options = null) {
try {
if ($res instanceof \phpseclib3\Crypt\Common\PrivateKey /*\phpseclib3\Crypt\RSA\PrivateKey*/ ) {
$privKey = $res;
if (!is_null($passphrase)) {
$privKey = $res->withPassword($passphrase);
}
$privKey = $privKey."";
return true;
} else if (is_string($res)) {
$privKey = $res;
if (!is_null($passphrase)) {
$privKey = \phpseclib3\Crypt\RSA::load($privKey);
$privKey = $res->withPassword($passphrase);
$privKey = $privKey."";
}
return true;
} else if (is_array($res)) {
$privKey = $res[2]."";
if (!is_null($passphrase)) {
$privKey = \phpseclib3\Crypt\RSA::load($privKey);
$privKey = $res->withPassword($passphrase);
$privKey = $privKey."";
}
return true;
} else {
throw new Exception("Invalid input datatype");
}
} catch (Exception $e) {
global $openssl_supplement_last_error;
$openssl_supplement_last_error = $e->getMessage();
return false;
}
}
 
function openssl_pkey_get_details($res) {
return array(
"bits" => $res[1],
"key" => $res[3]."",
"type" => $res[0]
);
}
 
function openssl_public_encrypt($data, &$encrypted, $pubKey) {
try {
if (is_string($pubKey)) $pubKey = openssl_pkey_get_public($pubKey);
if (!is_object($pubKey) || !method_exists($pubKey,'encrypt'))
throw new Exception("Invalid input datatype");
$encrypted = $pubKey->encrypt($data);
return true;
} catch (Exception $e) {
global $openssl_supplement_last_error;
$openssl_supplement_last_error = $e->getMessage();
return false;
}
}
 
function openssl_private_decrypt($encrypted, &$decrypted, $privKey) {
try {
if (is_string($privKey)) $privKey = openssl_pkey_get_private($privKey);
if (!is_object($privKey) || !method_exists($privKey,'decrypt'))
throw new Exception("Invalid input datatype");
$decrypted = $privKey->decrypt($encrypted);
return true;
} catch (Exception $e) {
global $openssl_supplement_last_error;
$openssl_supplement_last_error = $e->getMessage();
return false;
}
}
 
function openssl_verify($msg, $signature, $public, $algorithm=OPENSSL_ALGO_SHA1) {
try {
if ($algorithm == OPENSSL_ALGO_SHA1) $algorithm = 'SHA1';
if ($algorithm == OPENSSL_ALGO_SHA224) $algorithm = 'SHA224';
if ($algorithm == OPENSSL_ALGO_SHA256) $algorithm = 'SHA256';
if ($algorithm == OPENSSL_ALGO_SHA384) $algorithm = 'SHA384';
if ($algorithm == OPENSSL_ALGO_SHA512) $algorithm = 'SHA512';
if ($algorithm == OPENSSL_ALGO_RMD160) $algorithm = 'RMD160';
if ($algorithm == OPENSSL_ALGO_MD5) $algorithm = 'MD5';
if ($algorithm == OPENSSL_ALGO_MD4) $algorithm = 'MD4';
if (is_string($public)) $public = openssl_pkey_get_public($public);
if (!is_object($public) || !method_exists($public,'verify'))
throw new Exception("Invalid input datatype");
return $public->withHash($algorithm)->verify($msg, $signature) ? 1 : 0;
} catch (Exception $e) {
global $openssl_supplement_last_error;
$openssl_supplement_last_error = $e->getMessage();
return false;
}
}
 
function openssl_sign($msg, &$signature, $private, $algorithm=OPENSSL_ALGO_SHA1) {
try {
if ($algorithm == OPENSSL_ALGO_SHA1) $algorithm = 'SHA1';
if ($algorithm == OPENSSL_ALGO_SHA224) $algorithm = 'SHA224';
if ($algorithm == OPENSSL_ALGO_SHA256) $algorithm = 'SHA256';
if ($algorithm == OPENSSL_ALGO_SHA384) $algorithm = 'SHA384';
if ($algorithm == OPENSSL_ALGO_SHA512) $algorithm = 'SHA512';
if ($algorithm == OPENSSL_ALGO_RMD160) $algorithm = 'RMD160';
if ($algorithm == OPENSSL_ALGO_MD5) $algorithm = 'MD5';
if ($algorithm == OPENSSL_ALGO_MD4) $algorithm = 'MD4';
if (is_string($private)) $private = openssl_pkey_get_private($private);
if (!is_object($private) || !method_exists($private,'sign'))
throw new Exception("Invalid input datatype");
$signature = $private->withHash($algorithm)->sign($msg);
return true;
} catch (Exception $e) {
global $openssl_supplement_last_error;
$openssl_supplement_last_error = $e->getMessage();
return false;
}
}
 
function openssl_error_string() {
global $openssl_supplement_last_error;
$res = $openssl_supplement_last_error;
$openssl_supplement_last_error = '';
return $res;
}
 
function openssl_random_pseudo_bytes($len) {
/*
if (function_exists('openssl_random_pseudo_bytes')) {
$a = openssl_random_pseudo_bytes($len);
if ($a) return $a;
}
*/
 
if (function_exists('mcrypt_create_iv')) {
$a = bin2hex(mcrypt_create_iv($len, MCRYPT_DEV_URANDOM));
if ($a) return $a;
}
 
if (function_exists('random_bytes')) {
$a = random_bytes($len);
if ($a) return $a;
}
 
// Fallback to non-secure RNG
$a = '';
while (strlen($a) < $len*2) {
$a .= sha1(uniqid((string)mt_rand(), true));
}
$a = substr($a, 0, $len*2);
return hex2bin($a);
}
 
function openssl_encrypt($data, $cipher_algo, $passphrase, $options=0, $iv="", &$tag=null, $aad="", $tag_length=16) {
try {
if (!is_null($tag)) throw new Exception("tag not implemented");
if ($aad != "") throw new Exception("aad not implemented");
if ($tag_length != 16) throw new Exception("tag_length not implemented");
if (!preg_match('@AES\\-(.+)\\-(.+)@i', $cipher_algo, $m)) throw new Exception("Algo not implemented");
if (($options & OPENSSL_ZERO_PADDING) != 0) throw new Exception("OPENSSL_ZERO_PADDING not implemented");
$aes = new \phpseclib3\Crypt\AES($m[2]);
$aes->setKeyLength($m[1]);
$passphrase = substr($passphrase, 0, $m[1]/8);
$passphrase = str_pad($passphrase, $m[1]/8, "\0", STR_PAD_RIGHT);
$aes->setKey($passphrase);
$aes->setIV($iv);
$res = $aes->encrypt($data);
if (($options & OPENSSL_RAW_DATA) == 0) $res = base64_encode($res);
return $res;
} catch (Exception $e) {
global $openssl_supplement_last_error;
$openssl_supplement_last_error = $e->getMessage();
return false;
}
}
 
function openssl_decrypt($data, $cipher_algo, $passphrase, $options=0, $iv="", $tag=null, $aad="") {
try {
if (!is_null($tag)) throw new Exception("tag not implemented");
if ($aad != "") throw new Exception("aad not implemented");
if (!preg_match('@AES\\-(.+)\\-(.+)@i', $cipher_algo, $m)) throw new Exception("Algo not implemented");
if (($options & OPENSSL_ZERO_PADDING) != 0) throw new Exception("OPENSSL_ZERO_PADDING not implemented");
$aes = new \phpseclib3\Crypt\AES($m[2]);
$aes->setKeyLength($m[1]);
$passphrase = substr($passphrase, 0, $m[1]/8);
$passphrase = str_pad($passphrase, $m[1]/8, "\0", STR_PAD_RIGHT);
$aes->setKey($passphrase);
$aes->setIV($iv);
if (($options & OPENSSL_RAW_DATA) == 0) $data = base64_decode($data);
return $aes->decrypt($data);
} catch (Exception $e) {
global $openssl_supplement_last_error;
$openssl_supplement_last_error = $e->getMessage();
return false;
}
}
 
function openssl_free_key($key) {
// does nothing
}
 
function openssl_get_privatekey($key, $passphrase=null) {
return openssl_pkey_get_private($key, $passphrase=null);
}
 
function openssl_pkey_get_private($key, $passphrase=null) {
try {
if (substr($key,0,7) === 'file://') {
if (!file_exists($file = substr($key, 7))) throw new Exception("file not found");
$key = file_get_contents($file);
}
if (is_null($passphrase)) $passphrase = false;
$privKey = \phpseclib3\Crypt\RSA::load($key, $passphrase);
return $privKey->withPassword(false)->withPadding(\phpseclib3\Crypt\RSA::ENCRYPTION_PKCS1 | \phpseclib3\Crypt\RSA::SIGNATURE_PKCS1); /** @phpstan-ignore-line */ // Call to an undefined method phpseclib3\Crypt\Common\AsymmetricKey::withPadding().
} catch (Exception $e) {
global $openssl_supplement_last_error;
$openssl_supplement_last_error = $e->getMessage();
return false;
}
}
 
function openssl_get_publickey($public_key) {
return openssl_pkey_get_public($public_key);
}
 
function openssl_pkey_get_public($public_key) {
try {
if (substr($public_key,0,7) === 'file://') {
if (!file_exists($file = substr($public_key, 7))) throw new Exception("file not found");
$public_key = file_get_contents($file);
}
$pubKey = \phpseclib3\Crypt\RSA::load($public_key);
return $pubKey->withPadding(\phpseclib3\Crypt\RSA::ENCRYPTION_PKCS1 | \phpseclib3\Crypt\RSA::SIGNATURE_PKCS1); /** @phpstan-ignore-line */ // Call to an undefined method phpseclib3\Crypt\Common\AsymmetricKey::withPadding().
} catch (Exception $e) {
global $openssl_supplement_last_error;
$openssl_supplement_last_error = $e->getMessage();
return false;
}
}
 
function openssl_pkey_free($key) {
}
 
}
/trunk/phpstan.neon.dist
0,0 → 1,14
parameters:
level: 5
paths:
- .
excludePaths:
analyseAndScan:
- .phpstan.tmp
tmpDir: .phpstan.tmp
ignoreErrors:
#- '#is always (true|false)\.#'
- '#Call to function assert\(\) with false will always evaluate to false\.#'
#- '#with no typehint specified\.#'
 
 
/trunk/simplexml_supplement.inc.php
0,0 → 1,332
<?php
 
/*
* PHP SimpleXML-Supplement
* Copyright 2020 - 2021 Daniel Marschall, ViaThinkSoft
* Revision 2021-06-12
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
 
// ======== ATTENTION, PLEASE READ ========
// This supplement script was created to support rare PHP installations that
// do not contain SimpleXML, for example at PHP you need to explicitly
// install the package "php-xml" if you want to have SimpleXML (In the PHP
// documentation, it is written that SimpleXML is available to all, which is
// not true).
//
// Beware that the supplement behaves differently than the real SimpleXML!
// (If you know how to improve this, please feel free to send me a patch)
//
// Just a few differences towards the original SimpleXML
// - print_r() looks different
// - The supplement requires that an XML string begins with "<!DOCTYPE" or "<?xml",
// otherwise, the first element will not be stripped away
// - The supplement is slow because of regular expressions
// - Many functions like "asXML" are not implemented
// - There might be other incompatibilities
//
// So, if you want to use the SimpleXML supplement, then please carefully
// test it with your application if it works.
// ========================================
 
if (!function_exists('simplexml_load_string')) {
 
// We cannot store the number 0, 1, 2, ... as items in the SimpleXMLElement, because PHP 7.0 had a bug
// that prevented /get_object_vars() from working correctly
// https://stackoverflow.com/questions/46000541/get-object-vars-returning-different-results-depending-on-php-version
// https://stackoverflow.com/questions/4914376/failed-to-get-dynamic-instance-variables-via-phps-reflection/4914405#comment76610293_4914405
define('SIMPLEXML_SUPPLEMENT_MAGIC', '_SIMPLEXML_SUPPLEMENT_IDX_');
 
function _simplexml_supplement_isnumeric($x) {
return substr($x,0,strlen(SIMPLEXML_SUPPLEMENT_MAGIC)) === SIMPLEXML_SUPPLEMENT_MAGIC;
}
function _simplexml_supplement_getnumber($x) {
return (int)substr($x,strlen(SIMPLEXML_SUPPLEMENT_MAGIC));
}
function _simplexml_supplement_addnumberprefix($x) {
return SIMPLEXML_SUPPLEMENT_MAGIC.$x;
}
 
// We may not store the fields "position" and "attrs" in the SimpleXMLElement object,
// otherweise the typecast SimpleXMLElement=>array will include them
$_simplexml_supplement_properties = array();
 
function simplexml_load_file($file): SimpleXMLElement {
return simplexml_load_string(file_get_contents($file));
}
 
function simplexml_load_string($testxml): SimpleXMLElement {
$out = new SimpleXMLElement(); /** @phpstan-ignore-line */
 
$testxml = preg_replace('@<!\\-\\-.+\\-\\->@','',$testxml); // remove comments
$testxml = preg_replace('@<([^>\\s]+)\\s*/>@smU','<\\1></\\1>',$testxml); // <x/> => <x></x>
 
if ((stripos($testxml, '<?xml') !== false) || (stripos($testxml, '<!doctype') !== false)) {
$testxml = preg_replace('@<\\?.+\\?>@','',$testxml);
$testxml = preg_replace('@<!doctype.+>@i','',$testxml);
$m = array();
preg_match('@<(\\S+?)[^>]*>(.*)</\\1>@smU',$testxml,$m); // find root element
$root_element = $m[1];
} else {
$root_element = null;
}
 
$m = array();
preg_match_all('@<(\\S+?)([^>]*)>(.*)</\\1>@smU', $testxml, $m, PREG_SET_ORDER);
foreach ($m as $n) {
$name = $n[1];
$other = $n[2];
$val = $n[3];
 
// We are using chr(1) to avoid that <x> is parsed as child if it follows CDATA immediately
$val = str_replace('<![CDATA[', chr(1), $val);
$val = str_replace(']]>', chr(1), $val);
$val = trim($val);
 
$new = $out->addChild($name, $val);
 
$m2 = array();
preg_match_all('@(\S+)=\\"([^\\"]+)\\"@smU', $other, $m2, PREG_SET_ORDER);
foreach ($m2 as $n2) {
$att_name = $n2[1];
$att_val = $n2[2];
$new->addAttribute($att_name, $att_val);
}
}
 
if (!is_null($root_element)) {
$out = $out->$root_element;
}
 
return $out;
}
 
class SimpleXMLElement implements ArrayAccess, Iterator { /** @phpstan-ignore-line */
 
function __destruct() {
global $_simplexml_supplement_properties;
unset($_simplexml_supplement_properties[spl_object_hash($this)]);
}
 
public function addAttribute($name, $val) {
global $_simplexml_supplement_properties;
$_simplexml_supplement_properties[spl_object_hash($this)]['attrs'][$name] = $val;
}
 
public function attributes() {
global $_simplexml_supplement_properties;
return $_simplexml_supplement_properties[spl_object_hash($this)]['attrs'];
}
 
public function isSupplement() {
return true;
}
 
public function __construct($val=null) {
global $_simplexml_supplement_properties;
$_simplexml_supplement_properties[spl_object_hash($this)] = array(
"position" => 0,
"attrs" => array()
);
if (!is_null($val)) {
$this->{_simplexml_supplement_addnumberprefix(0)} = $val;
}
}
 
public function isArray() {
$vars = get_object_vars($this);
$max = -1;
foreach ($vars as $x => $dummy) {
if (!_simplexml_supplement_isnumeric($x)) {
$max = -1;
break;
} else {
$num = _simplexml_supplement_getnumber($x);
if ($num > $max) $max = $num;
}
}
return $max > 0;
}
 
public function addToArray($val) {
$vars = get_object_vars($this);
$max = -1;
foreach ($vars as $x => $dummy) {
if (!_simplexml_supplement_isnumeric($x)) {
$max = -1;
break;
} else {
$num = _simplexml_supplement_getnumber($x);
if ($num > $max) $max = $num;
}
}
$max++;
$this->{_simplexml_supplement_addnumberprefix($max)} = $val;
}
 
public function __toString() {
$data = get_object_vars($this);
if (is_array($data)) {
if (isset($data[_simplexml_supplement_addnumberprefix(0)])) {
return $data[_simplexml_supplement_addnumberprefix(0)];
} else {
return '';
}
} else { /** @phpstan-ignore-line */
return $data;
}
}
 
public function offsetExists($offset) {
return isset($this->$offset);
}
 
public function offsetGet($offset) {
return $this->$offset;
}
 
public function offsetSet($offset, $value) {
$this->$offset = $value;
}
 
public function offsetUnset($offset) {
unset($this->$offset);
}
 
public function __get($name) {
// Output nothing
return new SimpleXMLElement(); /** @phpstan-ignore-line */
}
 
public function addChild($name, $val=null) {
global $_simplexml_supplement_properties;
 
if ($val == null) $val = new SimpleXMLElement(); /** @phpstan-ignore-line */
 
if ((substr(trim($val),0,1) === '<') || (trim($val) == '')) {
$val = simplexml_load_string($val);
}
 
if (is_string($val)) $val = str_replace(chr(1), '', $val);
 
$data = get_object_vars($this);
 
if (!isset($data[$name])) {
if ($val instanceof SimpleXMLElement) {
$this->$name = $val;
} else {
$this->$name = new SimpleXMLElement($val);
}
} else {
if (!($val instanceof SimpleXMLElement)) {
$val = new SimpleXMLElement($val);
}
 
if ($data[$name]->isArray()) {
$data[$name]->addToArray($val);
} else {
$tmp = new SimpleXMLElement(); /** @phpstan-ignore-line */
$tmp->addToArray($data[$name]);
$tmp->addToArray($val);
$this->$name = $tmp;
$_simplexml_supplement_properties[spl_object_hash($this)]['attrs'] = array();
}
return $val;
}
 
return $this->$name;
}
 
public function rewind() {
global $_simplexml_supplement_properties;
$_simplexml_supplement_properties[spl_object_hash($this)]['position'] = 0;
}
 
public function current() {
global $_simplexml_supplement_properties;
$vars = get_object_vars($this);
$cnt = 0;
foreach ($vars as $x => $dummy) { /** @phpstan-ignore-line */
if (($dummy instanceof SimpleXMLElement) && !_simplexml_supplement_isnumeric($x) && $dummy->isArray()) {
$vars2 = get_object_vars($dummy);
foreach ($vars2 as $x2 => $dummy2) {
if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) {
if ($dummy2 instanceof SimpleXMLElement) {
return $dummy2; /** @phpstan-ignore-line */
} else {
return new SimpleXMLElement($dummy2); /** @phpstan-ignore-line */
}
}
$cnt++;
}
} else {
if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) {
if ($dummy instanceof SimpleXMLElement) {
return $dummy; /** @phpstan-ignore-line */
} else {
return new SimpleXMLElement($dummy); /** @phpstan-ignore-line */
}
}
$cnt++;
}
}
 
 
}
 
public function key() {
global $_simplexml_supplement_properties;
$vars = get_object_vars($this);
$cnt = 0;
foreach ($vars as $x => $dummy) { /** @phpstan-ignore-line */
if (($dummy instanceof SimpleXMLElement) && !_simplexml_supplement_isnumeric($x) && $dummy->isArray()) {
$vars2 = get_object_vars($dummy);
foreach ($vars2 as $x2 => $dummy2) {
if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) return $x/*sic*/;
$cnt++;
}
} else {
if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) return $x;
$cnt++;
}
}
}
 
public function next() {
global $_simplexml_supplement_properties;
++$_simplexml_supplement_properties[spl_object_hash($this)]['position'];
}
 
public function valid() {
global $_simplexml_supplement_properties;
 
$vars = get_object_vars($this);
$cnt = 0;
foreach ($vars as $x => $dummy) {
if (($dummy instanceof SimpleXMLElement) && !_simplexml_supplement_isnumeric($x) && $dummy->isArray()) {
$vars2 = get_object_vars($dummy);
foreach ($vars2 as $x2 => $dummy2) {
$cnt++;
}
} else {
$cnt++;
}
}
 
return $_simplexml_supplement_properties[spl_object_hash($this)]['position'] < $cnt;
}
 
}
}
/trunk/svn_utils.inc.php
0,0 → 1,111
<?php
 
/*
* PHP svn functions
* Copyright 2021 - 2023 Daniel Marschall, ViaThinkSoft
* Revision 2023-09-15
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
function get_svn_revision($dir='') {
if (!empty($dir)) $dir .= '/';
if (!is_dir($dir)) return false;
if (is_dir($dir.'/.svn')) $dir .= '/.svn/';
 
// Try to find out the SVN version using the shell
$output = @shell_exec('svnversion '.escapeshellarg($dir).' 2>&1');
$match = array();
if (preg_match('/\d+/', $output, $match)) {
return ($cachedVersion = $match[0]);
}
 
$output = @shell_exec('svn info '.escapeshellarg($dir).' 2>&1');
if (preg_match('/Revision:\s*(\d+)/m', $output, $match)) { // do not translate
return ($cachedVersion = $match[1]);
}
 
// If that failed, try to get the version via access of the database files
if (class_exists('SQLite3')) {
try {
$db = new SQLite3($dir.'.svn/wc.db');
$results = $db->query('SELECT MIN(revision) AS rev FROM NODES_BASE');
while ($row = $results->fetchArray()) {
return ($cachedVersion = $row['rev']);
}
$db->close();
$db = null;
} catch (Exception $e) {
}
}
if (class_exists('PDO')) {
try {
$pdo = new PDO('sqlite:'.$dir.'.svn/wc.db');
$res = $pdo->query('SELECT MIN(revision) AS rev FROM NODES_BASE');
$row = $res->fetch();
if ($row !== false) {
return ($cachedVersion = $row['rev']);
}
$pdo = null;
} catch (Exception $e) {
}
}
 
// We couldn't get the revision info
// Try parsing the binary file. It is a bit risky though...
return get_svn_revision_without_sqlite3($dir);
}
 
// Note: get_svn_revision_without_sqlite3() can be very unstable, so it is highly recommended to install php-sqlite in order to parse the file correctly.
function get_svn_revision_without_sqlite3($svn_path, $base='trunk') {
if (!empty($svn_path)) $svn_path .= '/';
if (!is_dir($svn_path)) return false;
if (!is_dir($svn_path.'/.svn')) $svn_path .= '/../';
 
$fil = file_get_contents($svn_path.'/.svn/wc.db');
preg_match_all('@('.preg_quote($base,'@').'/[a-z0-9!"#$%&\'()*+,.\/:;<=>?\@\[\] ^_`{|}~-]+)(..)normal(file|dir)@', $fil, $m, PREG_SET_ORDER);
 
$files = array();
foreach ($m as list($dummy, $fil, $revision, $type)) {
$val = hexdec(bin2hex($revision));
 
$tmp = explode("$base/", $fil);
$fil = end($tmp);
 
// TODO: Problem: We don't know if it was checked out as / or checked out as /trunk/, or something else!
if (!file_exists($svn_path."/$base/$fil") && !file_exists($svn_path."/$fil")) continue; // deleted files (deleted rows?!) might be still in the binary
 
if (!isset($files[$fil])) $files[$fil] = -1;
if ($files[$fil] < $val) $files[$fil] = $val;
}
$arr = array_values($files);
 
/*
foreach ($files as $name => $val) {
if ($val != 1228) echo "DEBUG Unexpected: $val / $fil\n";
}
*/
 
if (count($files) == 0) return 1; // should not happen
$num = count($arr);
$middleVal = floor(($num - 1) / 2);
if ($num % 2) {
$median = $arr[$middleVal];
} else {
$lowMid = $arr[$middleVal];
$highMid = $arr[$middleVal + 1];
$median = (($lowMid + $highMid) / 2);
}
 
return $median;
}
/trunk/vtor_get_contents.inc.php
0,0 → 1,10
<?php
 
define('UA_NAME', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)');
 
function file_get_contents2($url) {
$out = array();
exec("vtor -- wget -q -U ".escapeshellarg(UA_NAME)." -O - ".escapeshellarg($url), $out, $code);
if ($code != 0) return false;
return implode("\n", $out);
}
/trunk/vts_crypt.inc.php
0,0 → 1,747
<?php
 
/*
* ViaThinkSoft Modular Crypt Format 1.0 and vts_password_*() functions
* Copyright 2023 Daniel Marschall, ViaThinkSoft
* Revision 2023-03-03
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
/*
 
The function vts_password_hash() replaces password_hash()
and adds the ViaThinkSoft Modular Crypt Format 1.0 hash as well as
all hashes from password_hash() and crypt().
 
The function vts_password_verify() replaces password_verify().
 
ViaThinkSoft Modular Crypt Format 1.0 performs a simple hash or HMAC operation.
No key derivation function or iterations are performed.
Format:
$1.3.6.1.4.1.37476.3.0.1.1$a=<algo>,m=<mode>[,i=<iterations>]$<salt>$<hash>
where <algo> is any valid hash algorithm (name scheme of PHP hash_algos() preferred), e.g.
sha3-512
sha3-384
sha3-256
sha3-224
sha512
sha512/256
sha512/224
sha384
sha256
sha224
sha1
md5
Not supported are these hashes (because they have a special salt-handling and output their own crypt format):
bcrypt [Standardized crypt identifier 2, 2a, 2x, 2y]
argon2i [Crypt identifier argon2i, not standardized]
argon2id [Crypt identifier argon2i, not standardized]
Valid <mode> :
sp = salt + password
ps = password + salt
sps = salt + password + salt
hmac = HMAC (salt is the key)
pbkdf2 = PBKDF2-HMAC (Additional param i= contains the number of iterations)
<iterations> can be omitted if 0. It is required for mode=pbkdf2. For sp/ps/sps/hmac, it is optional.
Like most Crypt-hashes, <salt> and <hash> are Radix64 coded
with alphabet './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' and no padding.
Link to the online specification:
https://hosted.oidplus.com/viathinksoft/?goto=oid%3A1.3.6.1.4.1.37476.3.0.1.1
Reference implementation in PHP:
https://github.com/danielmarschall/php_utils/blob/master/vts_crypt.inc.php
 
*/
 
require_once __DIR__ . '/misc_functions.inc.php';
 
define('OID_MCF_VTS_V1', '1.3.6.1.4.1.37476.3.0.1.1'); // { iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) 37476 specifications(3) misc(0) modular-crypt-format(1) vts-crypt-v1(1) }
 
// Valid algorithms for vts_password_hash():
define('PASSWORD_STD_DES', 'std-des'); // Algorithm from crypt()
define('PASSWORD_EXT_DES', 'ext-des'); // Algorithm from crypt()
define('PASSWORD_MD5', 'md5'); // Algorithm from crypt()
define('PASSWORD_BLOWFISH', 'blowfish'); // Algorithm from crypt()
define('PASSWORD_SHA256', 'sha256'); // Algorithm from crypt()
define('PASSWORD_SHA512', 'sha512'); // Algorithm from crypt()
define('PASSWORD_VTS_MCF1', OID_MCF_VTS_V1); // Algorithm by ViaThinkSoft
// Other valid values (already defined in PHP):
// - PASSWORD_DEFAULT
// - PASSWORD_BCRYPT
// - PASSWORD_ARGON2I
// - PASSWORD_ARGON2ID
 
define('PASSWORD_VTS_MCF1_MODE_SP', 'sp'); // Salt+Password
define('PASSWORD_VTS_MCF1_MODE_PS', 'ps'); // Password+Salt
define('PASSWORD_VTS_MCF1_MODE_SPS', 'sps'); // Salt+Password+Salt
define('PASSWORD_VTS_MCF1_MODE_HMAC', 'hmac'); // HMAC
define('PASSWORD_VTS_MCF1_MODE_PBKDF2', 'pbkdf2'); // PBKDF2-HMAC
 
define('PASSWORD_EXT_DES_DEFAULT_ITERATIONS', 725);
define('PASSWORD_BLOWFISH_DEFAULT_COST', 10);
define('PASSWORD_SHA256_DEFAULT_ROUNDS', 5000);
define('PASSWORD_SHA512_DEFAULT_ROUNDS', 5000);
define('PASSWORD_VTS_MCF1_DEFAULT_ALGO', 'sha3-512'); // any value in hash_algos(), NOT vts_hash_algos()
define('PASSWORD_VTS_MCF1_DEFAULT_MODE', PASSWORD_VTS_MCF1_MODE_PS);
define('PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS', 0); // For PBKDF2, iterations=0 means: Default, depending on the algo
 
// --- Part 1: Modular Crypt Format encode/decode
 
function crypt_modular_format_encode($id, $bin_salt, $bin_hash, $params=null) {
// $<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
$out = '$'.$id;
if (!is_null($params)) {
$ary_params = array();
foreach ($params as $name => $value) {
$ary_params[] = "$name=$value";
}
$out .= '$'.implode(',',$ary_params);
}
$out .= '$'.crypt_radix64_encode($bin_salt);
$out .= '$'.crypt_radix64_encode($bin_hash);
return $out;
}
 
function crypt_modular_format_decode($mcf) {
$ary = explode('$', $mcf);
 
$dummy = array_shift($ary);
if ($dummy !== '') return false;
 
$dummy = array_shift($ary);
$id = $dummy;
 
$params = array();
$dummy = array_shift($ary);
if (strpos($dummy, '=') !== false) {
$params_ary = explode(',',$dummy);
foreach ($params_ary as $param) {
$bry = explode('=', $param, 2);
if (count($bry) > 1) {
$params[$bry[0]] = $bry[1];
}
}
} else {
array_unshift($ary, $dummy);
}
 
if (count($ary) > 1) {
$dummy = array_shift($ary);
$bin_salt = crypt_radix64_decode($dummy);
} else {
$bin_salt = '';
}
 
$dummy = array_shift($ary);
$bin_hash = crypt_radix64_decode($dummy);
 
return array('id' => $id,
'salt' => $bin_salt,
'hash' => $bin_hash,
'params' => $params);
}
 
// --- Part 2: ViaThinkSoft Modular Crypt Format 1.0
 
function vts_crypt_version($hash) {
if (str_starts_with($hash, '$'.OID_MCF_VTS_V1.'$')) {
return '1';
} else {
return '0';
}
}
 
function vts_crypt_hash($algo, $str_password, $str_salt, $ver='1', $mode=PASSWORD_VTS_MCF1_DEFAULT_MODE, $iterations=PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS) {
if ($ver == '1') {
if ($mode == PASSWORD_VTS_MCF1_MODE_SP) {
$bin_hash = hash_ex($algo, $str_salt.$str_password, true);
for ($i=0; $i<$iterations; $i++) {
$bin_hash = hash_ex($algo, $str_salt.$bin_hash.$i, true);
}
} else if ($mode == PASSWORD_VTS_MCF1_MODE_PS) {
$bin_hash = hash_ex($algo, $str_password.$str_salt, true);
for ($i=0; $i<$iterations; $i++) {
$bin_hash = hash_ex($algo, $bin_hash.$i.$str_salt, true);
}
} else if ($mode == PASSWORD_VTS_MCF1_MODE_SPS) {
$bin_hash = hash_ex($algo, $str_salt.$str_password.$str_salt, true);
for ($i=0; $i<$iterations; $i++) {
$bin_hash = hash_ex($algo, $str_salt.$bin_hash.$i.$str_salt, true);
}
} else if ($mode == PASSWORD_VTS_MCF1_MODE_HMAC) {
$bin_hash = hash_hmac_ex($algo, $str_password, $str_salt, true);
for ($i=0; $i<$iterations; $i++) {
// https://security.stackexchange.com/questions/149299/rounds-in-a-hashing-function
$bin_hash = hash_hmac_ex($algo, $str_password, $bin_hash.$i, true);
}
} else if ($mode == PASSWORD_VTS_MCF1_MODE_PBKDF2) {
// Note: If $iterations=0, then hash_pbkdf2_ex() will correct it to the best value depending on $algo, see _vts_password_default_iterations().
$bin_hash = hash_pbkdf2_ex($algo, $str_password, $str_salt, $iterations, 0, true);
} else {
throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, hmac, or pbkdf2.");
}
$bin_salt = $str_salt;
$params = array();
$params['a'] = $algo;
$params['m'] = $mode;
if ($iterations != 0) $params['i'] = $iterations; // i can be omitted if it is 0.
return crypt_modular_format_encode(OID_MCF_VTS_V1, $bin_salt, $bin_hash, $params);
} else {
throw new Exception("Invalid VTS crypt version, expect 1.");
}
}
 
function vts_crypt_verify($password, $hash): bool {
$ver = vts_crypt_version($hash);
if ($ver == '1') {
// Decode the MCF hash parameters
$data = crypt_modular_format_decode($hash);
if ($data === false) throw new Exception('Invalid auth key');
$id = $data['id'];
$bin_salt = $data['salt'];
$bin_hash = $data['hash'];
$params = $data['params'];
 
if (!isset($params['a'])) throw new Exception('Param "a" (algo) missing');
$algo = $params['a'];
 
if (!isset($params['m'])) throw new Exception('Param "m" (mode) missing');
$mode = $params['m'];
 
if ($mode == PASSWORD_VTS_MCF1_MODE_PBKDF2) {
if (!isset($params['i'])) throw new Exception('Param "i" (iterations) missing');
$iterations = $params['i'];
} else {
$iterations = isset($params['i']) ? $params['i'] : 0;
}
 
// Create a VTS MCF 1.0 hash based on the parameters of $hash and the password $password
$calc_authkey_1 = vts_crypt_hash($algo, $password, $bin_salt, $ver, $mode, $iterations);
 
// We re-encode the MCF to make sure that it can be compared with the VTS MCF 1.0 (correct sorting of params etc.)
$calc_authkey_2 = crypt_modular_format_encode($id, $bin_salt, $bin_hash, $params);
 
return hash_equals($calc_authkey_2, $calc_authkey_1);
} else {
throw new Exception("Invalid VTS crypt version, expect 1.");
}
}
 
// --- Part 3: Replacement of vts_password_*() functions
 
/**
* This function replaces password_algos() by extending it with
* password hashes that are implemented in vts_password_hash().
* @return array of hashes that can be used in vts_password_hash().
*/
function vts_password_algos() {
$hashes = password_algos();
$hashes[] = PASSWORD_STD_DES; // Algorithm from crypt()
$hashes[] = PASSWORD_EXT_DES; // Algorithm from crypt()
$hashes[] = PASSWORD_MD5; // Algorithm from crypt()
$hashes[] = PASSWORD_BLOWFISH; // Algorithm from crypt()
$hashes[] = PASSWORD_SHA256; // Algorithm from crypt()
$hashes[] = PASSWORD_SHA512; // Algorithm from crypt()
$hashes[] = PASSWORD_VTS_MCF1; // Algorithm by ViaThinkSoft
return $hashes;
}
 
/** vts_password_get_info() is the same as password_get_info(),
* but it adds the crypt() and ViaThinkSoft MCF 1.0 algos which can be
* produced by vts_password_hash()
* @param string $hash Hash created by vts_password_hash(), password_hash(), or crypt().
* @return array Same output like password_get_info().
*/
function vts_password_get_info($hash) {
if (vts_crypt_version($hash) == '1') {
// OID_MCF_VTS_V1
$mcf = crypt_modular_format_decode($hash);
 
//$options['salt_length'] = strlen($mcf['salt']); // Note: salt_length is not an MCF option! It's just a hint for vts_password_hash()
 
if (!isset($mcf['params']['a'])) throw new Exception('Param "a" (algo) missing');
$options['algo'] = $mcf['params']['a'];
 
if (!isset($mcf['params']['m'])) throw new Exception('Param "m" (mode) missing');
$options['mode'] = $mcf['params']['m'];
 
if ($options['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2) {
if (!isset($mcf['params']['i'])) throw new Exception('Param "i" (iterations) missing');
$options['iterations'] = (int)$mcf['params']['i'];
} else {
$options['iterations'] = isset($mcf['params']['i']) ? (int)$mcf['params']['i'] : 0;
}
 
return array(
"algo" => PASSWORD_VTS_MCF1,
"algoName" => "vts-mcf-v1",
"options" => $options
);
} else if (!str_starts_with($hash, '$') && (strlen($hash) == 13)) {
// PASSWORD_STD_DES
return array(
"algo" => PASSWORD_STD_DES,
"algoName" => "std-des",
"options" => array(
// None
)
);
} else if (str_starts_with($hash, '_') && (strlen($hash) == 20)) {
// PASSWORD_EXT_DES
return array(
"algo" => PASSWORD_EXT_DES,
"algoName" => "ext-des",
"options" => array(
"iterations" => (int)base64_int_decode(substr($hash,1,4))
)
);
} else if (str_starts_with($hash, '$1$')) {
// PASSWORD_MD5
return array(
"algo" => PASSWORD_MD5,
"algoName" => "md5",
"options" => array(
// None
)
);
} else if (str_starts_with($hash, '$2$') || str_starts_with($hash, '$2a$') ||
str_starts_with($hash, '$2x$') || str_starts_with($hash, '$2y$')) {
// PASSWORD_BLOWFISH
return array(
"algo" => PASSWORD_BLOWFISH,
"algoName" => "blowfish",
"options" => array(
"cost" => (int)ltrim(explode('$',$hash)[2],'0')
)
);
} else if (str_starts_with($hash, '$5$')) {
// PASSWORD_SHA256
return array(
"algo" => PASSWORD_SHA256,
"algoName" => "sha256",
"options" => array(
'rounds' => (int)str_replace('rounds=','',explode('$',$hash)[2])
)
);
} else if (str_starts_with($hash, '$6$')) {
// PASSWORD_SHA512
return array(
"algo" => PASSWORD_SHA512,
"algoName" => "sha512",
"options" => array(
'rounds' => (int)str_replace('rounds=','',explode('$',$hash)[2])
)
);
} else {
// PASSWORD_DEFAULT
// PASSWORD_BCRYPT
// PASSWORD_ARGON2I
// PASSWORD_ARGON2ID
return password_get_info($hash);
}
}
 
/** This function extends password_hash() with the algorithms supported by crypt().
* It also adds vts_crypt_hash() which implements the ViaThinkSoft Modular Crypt Format 1.0.
* The result can be verified using vts_password_verify().
* @param string $password to be hashed
* @param mixed $algo algorithm
* @param array $options options for the hashing algorithm
* @return string Crypt style password hash
*/
function vts_password_hash($password, $algo, $options=array()): string {
$options = vts_password_fill_default_options($algo, $options);
 
$crypt_salt = null;
if (($algo === PASSWORD_STD_DES) && defined('CRYPT_STD_DES')) {
// Standard DES-based hash with a two character salt from the alphabet "./0-9A-Za-z". Using invalid characters in the salt will cause crypt() to fail.
$crypt_salt = des_compat_salt(2);
} else if (($algo === PASSWORD_EXT_DES) && defined('CRYPT_EXT_DES')) {
// Extended DES-based hash. The "salt" is a 9-character string consisting of an underscore followed by 4 characters of iteration count and 4 characters of salt. Each of these 4-character strings encode 24 bits, least significant character first. The values 0 to 63 are encoded as ./0-9A-Za-z. Using invalid characters in the salt will cause crypt() to fail.
$iterations = $options['iterations'];
$crypt_salt = '_' . base64_int_encode($iterations,4) . des_compat_salt(4);
} else if (($algo === PASSWORD_MD5) && defined('CRYPT_MD5')) {
// MD5 hashing with a twelve character salt starting with $1$
$crypt_salt = '$1$'.des_compat_salt(12).'$';
} else if (($algo === PASSWORD_BLOWFISH) && defined('CRYPT_BLOWFISH')) {
// Blowfish hashing with a salt as follows: "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z". Using characters outside of this range in the salt will cause crypt() to return a zero-length string. The two digit cost parameter is the base-2 logarithm of the iteration count for the underlying Blowfish-based hashing algorithm and must be in range 04-31, values outside this range will cause crypt() to fail. "$2x$" hashes are potentially weak; "$2a$" hashes are compatible and mitigate this weakness. For new hashes, "$2y$" should be used.
$algo = '$2y$'; // most secure
$cost = $options['cost'];
$crypt_salt = $algo.str_pad($cost,2,'0',STR_PAD_LEFT).'$'.des_compat_salt(22).'$';
} else if (($algo === PASSWORD_SHA256) && defined('CRYPT_SHA256')) {
// SHA-256 hash with a sixteen character salt prefixed with $5$. If the salt string starts with 'rounds=<N>$', the numeric value of N is used to indicate how many times the hashing loop should be executed, much like the cost parameter on Blowfish. The default number of rounds is 5000, there is a minimum of 1000 and a maximum of 999,999,999. Any selection of N outside this range will be truncated to the nearest limit.
$algo = '$5$';
$rounds = $options['rounds'];
$crypt_salt = $algo.'rounds='.$rounds.'$'.des_compat_salt(16).'$';
} else if (($algo === PASSWORD_SHA512) && defined('CRYPT_SHA512')) {
// SHA-512 hash with a sixteen character salt prefixed with $6$. If the salt string starts with 'rounds=<N>$', the numeric value of N is used to indicate how many times the hashing loop should be executed, much like the cost parameter on Blowfish. The default number of rounds is 5000, there is a minimum of 1000 and a maximum of 999,999,999. Any selection of N outside this range will be truncated to the nearest limit.
$algo = '$6$';
$rounds = $options['rounds'];
$crypt_salt = $algo.'rounds='.$rounds.'$'.des_compat_salt(16).'$';
}
 
if (!is_null($crypt_salt)) {
// Algorithms: PASSWORD_STD_DES
// PASSWORD_EXT_DES
// PASSWORD_MD5
// PASSWORD_BLOWFISH
// PASSWORD_SHA256
// PASSWORD_SHA512
$out = crypt($password, $crypt_salt);
if (strlen($out) < 13) throw new Exception("crypt() failed");
return $out;
} else if ($algo === PASSWORD_VTS_MCF1) {
// Algorithms: PASSWORD_VTS_MCF1
$ver = '1';
$algo = $options['algo'];
$mode = $options['mode'];
$iterations = $options['iterations'];
$salt_len = isset($options['salt_length']) ? $options['salt_length'] : 32; // Note: salt_length is not an MCF option! It's just a hint for vts_password_hash()
$salt = random_bytes_ex($salt_len, true, true);
return vts_crypt_hash($algo, $password, $salt, $ver, $mode, $iterations);
} else {
// Algorithms: PASSWORD_DEFAULT
// PASSWORD_BCRYPT
// PASSWORD_ARGON2I
// PASSWORD_ARGON2ID
return password_hash($password, $algo, $options);
}
}
 
/** This function replaces password_needs_rehash() by adding additional algorithms
* supported by vts_password_hash().
* @param string $hash The current hash
* @param string|int|null $algo Desired new default algo
* @param array $options Desired new default options
* @return bool True if algo or options of the current hash don't match the current desired values ($algo and $options), otherwise false.
*/
function vts_password_needs_rehash($hash, $algo, $options=array()) {
$options = vts_password_fill_default_options($algo, $options);
 
$info = vts_password_get_info($hash);
$algo2 = $info['algo'];
$options2 = $info['options'];
 
// Check if algorithm matches
if ($algo !== $algo2) return true;
 
if (vts_crypt_version($hash) == '1') {
if (isset($options['salt_length'])) {
// For VTS MCF 1.0, salt_length is a valid option for vts_password_hash(),
// but it is not a valid option inside the MCF options
// and it is not a valid option for vts_password_get_info().
unset($options['salt_length']);
}
 
// For PBKDF2, iterations=0 means: Default, depending on the algo
if (($options['iterations'] == 0/*default*/) && ($options2['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2)) {
$algo = $options2['algo'];
$userland = !hash_pbkdf2_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_pbkdf2');
$options['iterations'] = _vts_password_default_iterations($algo, $userland);
}
}
 
// Check if options match
if (count($options) !== count($options2)) return true;
foreach ($options as $name => $val) {
if ($options2[$name] != $val) return true;
}
return false;
}
 
/** This function extends password_verify() by adding ViaThinkSoft Modular Crypt Format 1.0.
* @param string $password to be checked
* @param string $hash Hash created by crypt(), password_hash(), or vts_password_hash().
* @return bool true if password is valid
*/
function vts_password_verify($password, $hash): bool {
if (vts_crypt_version($hash) != '0') {
// Hash created by vts_password_hash(), or vts_crypt_hash()
return vts_crypt_verify($password, $hash);
} else {
// Hash created by vts_password_hash(), password_hash(), or crypt()
return password_verify($password, $hash);
}
}
 
// --- Part 4: Functions which include a fallback to a pure-PHP sha3 implementation (requires https://github.com/danielmarschall/php-sha3 )
 
function hash_ex($algo, $data, $binary=false, $options=array()) {
if (!hash_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash')) {
$bits = (int)explode('-',$algo)[1];
$hash = \bb\Sha3\Sha3::hash($data, $bits, $binary);
} else {
$hash = hash($algo, $data, $binary);
}
return $hash;
}
 
function hash_hmac_ex($algo, $data, $key, $binary=false) {
if (!hash_hmac_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_hmac')) {
$bits = (int)explode('-',$algo)[1];
$hash = \bb\Sha3\Sha3::hash_hmac($data, $key, $bits, $binary);
} else {
$hash = hash_hmac($algo, $data, $key, $binary);
}
return $hash;
}
 
function hash_pbkdf2_ex($algo, $password, $salt, &$iterations=0, $length=0, $binary=false) {
if (!hash_pbkdf2_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_pbkdf2')) {
if ($iterations == 0/*default*/) {
$iterations = _vts_password_default_iterations($algo, true);
}
$bits = (int)explode('-',$algo)[1];
$hash = \bb\Sha3\Sha3::hash_pbkdf2($password, $salt, $iterations, $bits, $length, $binary);
} else {
if ($iterations == 0/*default*/) {
$iterations = _vts_password_default_iterations($algo, false);
}
$hash = hash_pbkdf2($algo, $password, $salt, $iterations, $length, $binary);
}
return $hash;
}
 
// --- Part 5: Useful functions required by the crypt-functions
 
define('BASE64_RFC4648_ALPHABET', '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/');
define('BASE64_CRYPT_ALPHABET', './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz');
 
function des_compat_salt($salt_len) {
if ($salt_len <= 0) return '';
$characters = BASE64_CRYPT_ALPHABET;
$salt = '';
$bytes = random_bytes_ex($salt_len, true, true);
for ($i=0; $i<$salt_len; $i++) {
$salt .= $characters[ord($bytes[$i]) % strlen($characters)];
}
return $salt;
}
 
function base64_int_encode($num, $len) {
// https://stackoverflow.com/questions/15534982/which-iteration-rules-apply-on-crypt-using-crypt-ext-des
$alphabet_raw = BASE64_CRYPT_ALPHABET;
$alphabet = str_split($alphabet_raw);
$arr = array();
$base = sizeof($alphabet);
while ($num) {
$rem = $num % $base;
$num = (int)($num / $base);
$arr[] = $alphabet[$rem];
}
$string = implode($arr);
return str_pad($string, $len, '.', STR_PAD_RIGHT);
}
 
function base64_int_decode($base64) {
$num = 0;
for ($i=strlen($base64)-1;$i>=0;$i--) {
$num += strpos(BASE64_CRYPT_ALPHABET, $base64[$i])*pow(strlen(BASE64_CRYPT_ALPHABET),$i);
}
return $num;
}
 
function crypt_radix64_encode($str) {
$x = $str;
$x = base64_encode($x);
$x = rtrim($x, '='); // remove padding
$x = strtr($x, BASE64_RFC4648_ALPHABET, BASE64_CRYPT_ALPHABET);
return $x;
}
 
function crypt_radix64_decode($str) {
$x = $str;
$x = strtr($x, BASE64_CRYPT_ALPHABET, BASE64_RFC4648_ALPHABET);
$x = base64_decode($x);
return $x;
}
 
function hash_supported_natively($algo) {
if (version_compare(PHP_VERSION, '5.1.2') >= 0) {
return in_array($algo, hash_algos());
} else {
return false;
}
}
 
function hash_hmac_supported_natively($algo): bool {
if (version_compare(PHP_VERSION, '7.2.0') >= 0) {
return in_array($algo, hash_hmac_algos());
} else if (version_compare(PHP_VERSION, '5.1.2') >= 0) {
return in_array($algo, hash_algos());
} else {
return false;
}
}
 
function hash_pbkdf2_supported_natively($algo) {
return hash_supported_natively($algo);
}
 
function vts_password_fill_default_options($algo, $options) {
if ($algo === PASSWORD_STD_DES) {
// No options
} else if ($algo === PASSWORD_EXT_DES) {
if (!isset($options['iterations'])) {
$options['iterations'] = PASSWORD_EXT_DES_DEFAULT_ITERATIONS;
}
} else if ($algo === PASSWORD_MD5) {
// No options
} else if ($algo === PASSWORD_BLOWFISH) {
if (!isset($options['cost'])) {
$options['cost'] = PASSWORD_BLOWFISH_DEFAULT_COST;
}
} else if ($algo === PASSWORD_SHA256) {
if (!isset($options['rounds'])) {
$options['rounds'] = PASSWORD_SHA256_DEFAULT_ROUNDS;
}
} else if ($algo === PASSWORD_SHA512) {
if (!isset($options['rounds'])) {
$options['rounds'] = PASSWORD_SHA512_DEFAULT_ROUNDS;
}
} else if ($algo === PASSWORD_VTS_MCF1) {
if (!isset($options['algo'])) {
$options['algo'] = PASSWORD_VTS_MCF1_DEFAULT_ALGO;
}
if (!isset($options['mode'])) {
$options['mode'] = PASSWORD_VTS_MCF1_DEFAULT_MODE;
}
if ($options['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2) {
if (!isset($options['iterations'])) {
$options['iterations'] = PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS;
}
} else {
$options['iterations'] = isset($options['iterations']) ? $options['iterations'] : 0;
}
}
return $options;
}
 
function _vts_password_default_iterations($algo, $userland) {
if ($userland) {
return 100; // because the userland implementation is EXTREMELY slow, we must choose a small value, sorry...
} else {
// Recommendations taken from https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
// Note that hash_pbkdf2() implements PBKDF2-HMAC-*
if ($algo == 'sha3-512') return 100000;
else if ($algo == 'sha3-384') return 100000;
else if ($algo == 'sha3-256') return 100000;
else if ($algo == 'sha3-224') return 100000;
else if ($algo == 'sha512') return 210000; // value by owasp.org cheatcheat (28 February 2023)
else if ($algo == 'sha512/256') return 210000; // value by owasp.org cheatcheat (28 February 2023)
else if ($algo == 'sha512/224') return 210000; // value by owasp.org cheatcheat (28 February 2023)
else if ($algo == 'sha384') return 600000;
else if ($algo == 'sha256') return 600000; // value by owasp.org cheatcheat (28 February 2023)
else if ($algo == 'sha224') return 600000;
else if ($algo == 'sha1') return 1300000; // value by owasp.org cheatcheat (28 February 2023)
else if ($algo == 'md5') return 5000000;
else return 5000;
}
}
 
// --- Part 6: Selftest
 
/*
for ($i=0; $i<9999; $i++) {
assert($i===base64_int_decode(base64_int_encode($i,4)));
}
 
$rnd = random_bytes_ex(50, true, true);
assert(crypt_radix64_decode(crypt_radix64_encode($rnd)) === $rnd);
 
$password = random_bytes_ex(20, false, true);
 
assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_STD_DES)));
//echo "'$dummy' ".strlen($dummy)."\n";
//var_dump(vts_password_get_info($dummy));
 
assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_EXT_DES)));
//echo "'$dummy' ".strlen($dummy)."\n";
//var_dump(vts_password_get_info($dummy));
 
assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_MD5)));
//echo "'$dummy' ".strlen($dummy)."\n";
//var_dump(vts_password_get_info($dummy));
 
assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_BLOWFISH)));
//echo "'$dummy' ".strlen($dummy)."\n";
//var_dump(vts_password_get_info($dummy));
 
assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_SHA256)));
//echo "'$dummy' ".strlen($dummy)."\n";
//var_dump(vts_password_get_info($dummy));
 
assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_SHA512)));
//echo "'$dummy' ".strlen($dummy)."\n";
//var_dump(vts_password_get_info($dummy));
 
assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_VTS_MCF1, array(
'algo' => 'sha3-512',
'mode' => 'pbkdf2',
'iterations' => 0
))));
//echo "'$dummy' ".strlen($dummy)."\n";
//var_dump(vts_password_get_info($dummy));
assert(false===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array(
'salt_length' => 51,
'algo' => 'sha3-512',
'mode' => 'pbkdf2',
'iterations' => 0
)));
assert(true===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array(
'salt_length' => 50,
'algo' => 'sha3-256',
'mode' => 'pbkdf2',
'iterations' => 0
)));
 
assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_VTS_MCF1, array(
'algo' => 'sha3-512',
'mode' => 'sps',
'iterations' => 2
))));
//echo "'$dummy' ".strlen($dummy)."\n";
//var_dump(vts_password_get_info($dummy));
assert(false===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array(
'salt_length' => 51,
'algo' => 'sha3-512',
'mode' => 'sps',
'iterations' => 2
)));
assert(true===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array(
'salt_length' => 50,
'algo' => 'sha3-256',
'mode' => 'sps',
'iterations' => 2
)));
 
assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_VTS_MCF1, array(
'algo' => 'sha3-512',
'mode' => 'hmac',
'iterations' => 2
))));
//echo "'$dummy' ".strlen($dummy)."\n";
//var_dump(vts_password_get_info($dummy));
assert(false===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array(
'salt_length' => 51,
'algo' => 'sha3-512',
'mode' => 'hmac',
'iterations' => 2
)));
assert(true===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array(
'salt_length' => 50,
'algo' => 'sha3-256',
'mode' => 'hmac',
'iterations' => 2
)));
*/
/trunk/x_509_utils.inc.php
0,0 → 1,404
<?php
 
/*
* X.509 Utilities for PHP
* Copyright 2011-2021 Daniel Marschall, ViaThinkSoft
* Version 2021-12-29
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
# define('OPENSSL_EXEC', 'openssl');
# define('OPENSSL_EXEC', 'torify openssl');
define('OPENSSL_EXEC', 'vtor -cr 1 -- openssl');
 
# ToDo: For every function 2 modes: certFile, certPEM
 
function x_509_matching_issuer($cert, $issuer) {
exec(OPENSSL_EXEC.' verify -purpose any -CApath /dev/null -CAfile '.escapeshellarg($issuer).' '.escapeshellarg($cert), $out, $code);
$out = implode("\n", $out);
# Ab 1.0 wird hier ein Errorcode zurückgeliefert
# if ($code != 0) return false;
 
# TODO
# error 20 at 0 depth lookup:unable to get local issuer certificate
$chain0_ok = strpos($out, "error 2 at 1 depth lookup:unable to get issuer certificate") !== false;
$all_ok = substr($out, -2) == 'OK';
 
$ok = $chain0_ok | $all_ok;
 
return $ok;
}
 
function x_509_is_crl_file($infile) { # Only PEM files
$cx = file($infile);
return trim($cx[0]) == '-----BEGIN X509 CRL-----';
}
 
function x_509_chain($infile, $CApath) {
$chain = array();
$chain[] = $infile;
 
while (true) {
$out = array();
exec(OPENSSL_EXEC.' x509 -issuer_hash -in '.escapeshellarg($infile).' -noout', $out, $code);
if ($code != 0) return false;
$hash = $out[0];
unset($out);
 
# $ary = glob($CApath . $hash . '.*');
# $aryr = glob($CApath . $hash . '.r*');
 
$ary = array();
$aryr = array();
$all_trusted = @glob($CApath . '*.pem');
if ($all_trusted) foreach ($all_trusted as &$a) {
if (x_509_is_crl_file($a)) {
$out = array();
exec(OPENSSL_EXEC.' crl -hash -noout -in '.escapeshellarg($a), $out, $code);
if ($code != 0) return false;
$this_hash = trim($out[0]);
unset($out);
# echo "CRL $a : $this_hash == $hash<br>\n";
if ($this_hash == $hash) {
$aryr[] = $a;
}
if ($code != 0) return false;
} else {
$out = array();
exec(OPENSSL_EXEC.' x509 -subject_hash -noout -in '.escapeshellarg($a), $out, $code);
if ($code != 0) return false;
$this_hash = trim($out[0]);
unset($out);
# echo "CERT $a : $this_hash == $hash<br>\n";
if ($this_hash == $hash) {
$ary[] = $a;
}
}
}
 
$found = false;
# echo "Searching issuer for $infile... (Hash = $hash)<br>\n";
foreach ($ary as &$a) {
if (in_array($a, $aryr)) continue;
 
# echo "Check $a...<br>\n";
if (x_509_matching_issuer($infile, $a)) {
# echo "Found! New file is $a<br>\n";
$found = true;
$infile = $a;
 
if (in_array($a, $chain)) {
# echo "Finished.\n";
return $chain;
}
 
$chain[] = $a;
break;
}
}
if (!$found) {
# echo "No issuer found!\n";
return false;
}
}
}
 
function x_509_get_ocsp_uris($infile) {
exec(OPENSSL_EXEC.' x509 -ocsp_uri -in '.escapeshellarg($infile).' -noout', $out, $code);
if ($code != 0) return false;
return $out;
}
 
 
// TODO: Needs caching, otherwise the page is too slow
function x_509_ocsp_check_chain($infile, $CApath) {
$x = x_509_chain($infile, $CApath);
 
if ($x === false) {
return 'Error: Could not complete chain!';
}
 
# echo 'Chain: ';
# print_r($x);
 
$found_ocsp = false;
$diag_nonce_err = false;
$diag_verify_err = false;
$diag_revoked = false;
$diag_unknown = false;
 
foreach ($x as $n => &$y) {
if (isset($x[$n+1])) {
$issuer = $x[$n+1];
} else {
$issuer = $y; // Root
}
 
$uris = x_509_get_ocsp_uris($y);
 
foreach ($uris as &$uri) {
$found_ocsp = true;
 
$out = array();
$xx = parse_url($uri);
$host = $xx['host'];
# $cmd = OPENSSL_EXEC." ocsp -issuer ".escapeshellarg($issuer)." -cert ".escapeshellarg($y)." -url ".escapeshellarg($uri)." -CApath ".escapeshellarg($CApath)." -VAfile ".escapeshellarg($issuer)." -nonce -header 'HOST' ".escapeshellarg($host)." -header 'User-Agent' 'Mozilla/5.0 (Windows NT 6.1; rv23.0) Gecko/20100101 Firefox/23.0' 2>&1" /* -text */;
# TODO: trusted.pem nicht hartcoden
$cmd = OPENSSL_EXEC." ocsp -issuer ".escapeshellarg($issuer)." -cert ".escapeshellarg($y)." -url ".escapeshellarg($uri)." -CAfile ".escapeshellarg($CApath.'/../trusted.pem')." -VAfile ".escapeshellarg($issuer)." -nonce -header 'HOST' ".escapeshellarg($host)." -header 'User-Agent' 'Mozilla/5.0 (Windows NT 6.1; rv23.0) Gecko/20100101 Firefox/23.0' 2>&1" /* -text */;
#echo $cmd;
exec($cmd, $out, $code);
if ($code != 0) {
if (($out[0] == 'Error querying OCSP responsder') ||
($out[0] == 'Error querying OCSP responder')) {
# TODO: openssl has a typo 'Error querying OCSP responsder'
# TODO: why does this error occour for comodo CA?
return "Error querying OCSP responder (Code $code)";
}
# print_r($out);
return 'Error: OpenSSL-Exec failure ('.$code.')!';
}
 
$outc = implode("\n", $out);
if (strpos($outc, "Response verify OK") === false) $diag_verify_err = true;
if (strpos($outc, "WARNING: no nonce in response") !== false) $diag_nonce_err = true;
# We are currently not watching for other warnings (ToDo)
 
if (strpos($outc, "$y: unknown") !== false) {
$diag_unknown = true;
} else if (strpos($outc, "$y: revoked") !== false) {
$diag_revoked = true;
} else if (strpos($outc, "$y: good") === false) {
#echo "C = $outc<br>\n";
# TODO:
# COMODO sagt
# C = Responder Error: unauthorized
# STARTCOM sagt
# C = Responder Error: malformedrequest
return "Error: Unexpected OCSP state! ($outc)";
}
 
# print_r($out);
unset($out);
}
}
 
# echo "Found OCSP = ".($found_ocsp ? 1 : 0)."\n";
# echo "Diag Nonce Error = ".($diag_nonce_err ? 1 : 0)."\n";
# echo "Diag Verify Error = ".($diag_verify_err ? 1 : 0)."\n";
# echo "Diag Revoked Error = ".($diag_revoked ? 1 : 0)."\n";
# echo "Diag Unknown Error = ".($diag_unknown ? 1 : 0)."\n";
 
if (!$found_ocsp) {
return 'No OCSP responders found in chain.';
}
 
if ($diag_verify_err) {
return 'Error: OCSP Verification failure!';
}
 
if ($diag_revoked) {
return 'Error: Some certs are revoked!';
}
 
if ($diag_unknown) {
return 'Warning: Some certs have unknown state!';
}
 
if ($diag_nonce_err) {
return 'OK, but NONCE missing';
}
 
return 'OK';
}
 
function _opensslVerify($cert, $mode = 0, $crl_mode = 0) {
# mode
# 0 = cert is a file
# 1 = cert is pem string
 
# crl_mode
# 0 = no crl check
# 1 = 1 crl check
# 2 = all crl check
 
$params = '';
if ($crl_mode == 0) {
$params = '';
} else if ($crl_mode == 1) {
$params = '-crl_check ';
} else if ($crl_mode == 2) {
$params = '-crl_check_all ';
} else {
return false;
}
 
if ($mode == 0) {
# $cmd = OPENSSL_EXEC.' verify '.$params.' -CApath '.escapeshellarg(__DIR__.'/../ca/trusted/').' '.escapeshellarg($cert);
$cmd = OPENSSL_EXEC.' verify '.$params.' -CAfile '.escapeshellarg(__DIR__.'/../ca/trusted.pem').' '.escapeshellarg($cert);
} else if ($mode == 1) {
# $cmd = 'echo '.escapeshellarg($cert).' | '.OPENSSL_EXEC.' verify '.$params.' -CApath '.escapeshellarg(__DIR__.'/../ca/trusted/');
$cmd = 'echo '.escapeshellarg($cert).' | '.OPENSSL_EXEC.' verify '.$params.' -CAfile '.escapeshellarg(__DIR__.'/../ca/trusted.pem');
} else {
return false;
}
$out = array();
exec($cmd, $out, $code);
 
if ($code != 0) return false;
 
return $out;
}
 
function opensslVerify($cert, $mode = 0) {
# 0 = cert is a file
# 1 = cert is pem string
 
$out = _opensslVerify($cert, $mode, 0);
if ($out === false) return 'Internal error';
$outtext = implode("\n", $out);
 
$out_crl = _opensslVerify($cert, $mode, 2);
if ($out_crl === false) return 'Internal error';
$outtext_crl = implode("\n", $out_crl);
 
if (strpos($outtext, "unable to get local issuer certificate") !== false) {
return 'CA unknown';
} else if (strpos($outtext, "certificate signature failure") !== false) {
return 'Fraudulent!';
}
 
$stat_expired = (strpos($outtext, "certificate has expired") !== false);
$stat_revoked = (strpos($outtext_crl, "certificate revoked") !== false);
 
# (ToDo) We are currently not looking for warnings
# $stat_crl_expired = (strpos($outtext_crl, "CRL has expired") !== false);
 
if ($stat_expired && $stat_revoked) {
return 'Expired & Revoked';
} else if ($stat_revoked) {
return 'Revoked';
} else if ($stat_expired) {
return 'Expired';
}
 
if (strpos($out[0], ': OK') !== false) {
return 'Verified';
}
 
return 'Unknown error';
}
 
function getTextdump($cert, $mode = 0, $format = 0) {
# mode
# 0 = cert is a file
# 1 = cert is pem string
 
# format
# 0 = normal
# 1 = nameopt
 
if ($format == 0) {
$params = '';
} else if ($format == 1) {
$params = ' -nameopt "esc_ctrl, esc_msb, sep_multiline, space_eq, lname"';
} else {
return false;
}
 
if ($mode == 0) {
exec(OPENSSL_EXEC.' x509 -noout -text'.$params.' -in '.escapeshellarg($cert), $out, $code);
} else if ($mode == 1) {
exec('echo '.escapeshellarg($cert).' | '.OPENSSL_EXEC.' x509 -noout -text'.$params, $out, $code);
} else {
return false;
}
 
if ($code != 0) return false;
 
$text = implode("\n", $out);
 
$text = str_replace("\n\n", "\n", $text); # TODO: repeat until no \n\n exist anymore
 
return $text;
}
 
function getAttributes($cert, $mode = 0, $issuer = false, $longnames = false) {
# mode
# 0 = cert is a file
# 1 = cert is pem string
 
if ($longnames) {
$params = ' -nameopt "esc_ctrl, esc_msb, sep_multiline, space_eq, lname"';
} else {
$params = ' -nameopt "esc_ctrl, esc_msb, sep_multiline, space_eq"';
}
 
if ($issuer) {
$params .= ' -issuer';
} else {
$params .= ' -subject';
}
 
if ($mode == 0) {
exec(OPENSSL_EXEC.' x509 -noout'.$params.' -in '.escapeshellarg($cert), $out, $code);
} else if ($mode == 1) {
exec('echo '.escapeshellarg($cert).' | '.OPENSSL_EXEC.' x509 -noout'.$params, $out, $code);
} else {
return false;
}
 
$attributes = array();
foreach ($out as $n => &$o) {
if ($n == 0) continue;
preg_match("| (.*) = (.*)$|ismU", $o, $m);
if (!isset($attributes[$m[1]])) $attributes[$m[1]] = array();
$attributes[$m[1]][] = $m[2];
}
 
return $attributes;
}
 
function openssl_get_sig_base64($cert, $mode = 0) {
# mode
# 0 = cert is a file
# 1 = cert is pem string
 
$params = '';
 
$out = array();
if ($mode == 0) {
exec(OPENSSL_EXEC.' x509 -noout'.$params.' -in '.escapeshellarg($cert), $out, $code);
} else if ($mode == 1) {
exec('echo '.escapeshellarg($cert).' | '.OPENSSL_EXEC.' x509 -noout'.$params, $out, $code);
} else {
return false;
}
$dump = implode("\n", $out);
 
/*
 
Signature Algorithm: sha1WithRSAEncryption
65:f0:6f:f0:1d:66:a4:fe:d1:38:85:6f:5e:06:7b:f3:a7:08:
...
1a:13:37
 
*/
 
$regex = "@\n {4}Signature Algorithm: (\S+)\n(( {8}([a-f0-9][a-f0-9]:){18}\n)* {8}([a-f0-9][a-f0-9](:[a-f0-9][a-f0-9]){0,17}\n))@sm";
preg_match_all($regex, "$dump\n", $m);
if (!isset($m[2][0])) return false;
$x = preg_replace("@[^a-z0-9]@", "", $m[2][0]);
$x = hex2bin($x);
return base64_encode($x);
}
/trunk/xml_utils.inc.php
0,0 → 1,192
<?php
 
/*
* XML Encoding Utilities
* Copyright 2011-2021 Daniel Marschall, ViaThinkSoft
* Version 1.8 (2021-11-24)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
// http://www.viathinksoft.de/?page=codelib&showid=89
 
// Unicode-proof htmlentities.
// Returns 'normal' chars as chars and weirdos as numeric html entites.
// Source: http://www.php.net/manual/en/function.htmlentities.php#107985 ; modified
// Modified by Daniel Marschall, ViaThinkSoft
function htmlentities_numeric($str, $allow_html=false, $encode_linebreaks=false) {
// Convert $str to UTF-8 if it is not already
if (mb_detect_encoding($str, "auto", true) != 'UTF-8') {
# $str = mb_convert_encoding($str, 'UTF-8', 'Windows-1252');
# $str = mb_convert_encoding($str, 'UTF-8', 'auto');
$str = mb_convert_encoding($str, 'UTF-8');
}
 
// get rid of existing entities else double-escape
// DM 24.08.2016 Removed because of OIDplus 1.0 XML export
//$str = html_entity_decode(stripslashes($str),ENT_QUOTES,'UTF-8');
 
$ar = preg_split('/(?<!^)(?!$)/u', $str); // return array of every multi-byte character
$str2 = '';
foreach ($ar as $c) {
$o = ord($c);
if (
(strlen($c) > 1) || /* multi-byte [unicode] */
($o < 32 || $o > 126) || /* <- control / latin weirdos -> */
($o > 33 && $o < 40) || /* quotes + ampersand */
($o > 59 && $o < 63) /* html */
) {
// convert to numeric entity
$c = mb_encode_numericentity($c, array(0x0, 0xffff, 0, 0xffff), 'UTF-8');
 
if ($allow_html) {
if ($c == '&#60;') $c = '<';
if ($c == '&#62;') $c = '>';
if ($c == '&#61;') $c = '=';
if ($c == '&#34;') $c = '"';
if ($c == '&#39;') $c = '\'';
if ($c == '&#38;') $c = '&'; // DM 24.08.2016 Re-added because OIDplus 1.0 XML export
}
 
if (!$encode_linebreaks) {
if ($allow_html) {
if ($c == "&#10;") $c = "<br />";
if ($c == "&#13;") $c = "<br />";
} else {
if ($c == "&#10;") $c = "\n";
if ($c == "&#13;") $c = "\r";
}
}
}
$str2 .= $c;
}
return $str2;
}
 
function ordUTF8($c, $index = 0, &$bytes = null) {
// http://de.php.net/manual/en/function.ord.php#78032
 
$len = strlen($c);
$bytes = 0;
 
if ($index >= $len) {
return false;
}
 
$h = ord($c[$index]);
 
if ($h <= 0x7F) {
$bytes = 1;
return $h;
} else if ($h < 0xC2) {
return false;
} else if ($h <= 0xDF && $index < $len - 1) {
$bytes = 2;
return ($h & 0x1F) << 6 | (ord($c[$index + 1]) & 0x3F);
} else if ($h <= 0xEF && $index < $len - 2) {
$bytes = 3;
return ($h & 0x0F) << 12 | (ord($c[$index + 1]) & 0x3F) << 6
| (ord($c[$index + 2]) & 0x3F);
} else if ($h <= 0xF4 && $index < $len - 3) {
$bytes = 4;
return ($h & 0x0F) << 18 | (ord($c[$index + 1]) & 0x3F) << 12
| (ord($c[$index + 2]) & 0x3F) << 6
| (ord($c[$index + 3]) & 0x3F);
} else {
return false;
}
}
 
function utf16_to_utf8($str) {
// http://betamode.de/2008/09/08/php-utf-16-zu-utf-8-konvertieren/
// http://www.moddular.org/log/utf16-to-utf8
 
$c0 = ord($str[0]);
$c1 = ord($str[1]);
if ($c0 == 0xFE && $c1 == 0xFF) {
$be = true;
} else if ($c0 == 0xFF && $c1 == 0xFE) {
$be = false;
} else {
return $str;
}
$str = substr($str, 2);
$len = strlen($str);
$dec = '';
for ($i = 0; $i < $len; $i += 2) {
$c = ($be) ? ord($str[$i]) << 8 | ord($str[$i + 1]) :
ord($str[$i + 1]) << 8 | ord($str[$i]);
if ($c >= 0x0001 && $c <= 0x007F) {
$dec .= chr($c);
} else if ($c > 0x07FF) {
$dec .= chr(0xE0 | (($c >> 12) & 0x0F));
$dec .= chr(0x80 | (($c >> 6) & 0x3F));
$dec .= chr(0x80 | (($c >> 0) & 0x3F));
} else {
$dec .= chr(0xC0 | (($c >> 6) & 0x1F));
$dec .= chr(0x80 | (($c >> 0) & 0x3F));
}
}
return $dec;
}
 
function html_named_to_numeric_entities($str) {
$str = mb_convert_encoding($str, 'UTF-8');
return mb_htmlentities(decodeNamedEntities($str));
}
 
if (!function_exists('decodeNamedEntities')) {
function decodeNamedEntities($string) {
// https://stackoverflow.com/questions/20406599/how-to-encode-for-entity-igrave-not-defined-error-in-xml-feed
static $entities = NULL;
if (NULL === $entities) {
$entities = array_flip(
array_diff(
get_html_translation_table(HTML_ENTITIES, ENT_COMPAT | ENT_HTML401, 'UTF-8'),
get_html_translation_table(HTML_ENTITIES, ENT_COMPAT | ENT_XML1, 'UTF-8')
)
);
}
return str_replace(array_keys($entities), $entities, $string);
}
}
 
if (!function_exists('mb_convert_encoding')) {
// https://riptutorial.com/php/example/15633/converting-unicode-characters-to-their-numeric-value-and-or-html-entities-using-php
function mb_convert_encoding($str, $to_encoding, $from_encoding = NULL) {
return iconv(($from_encoding === NULL) ? mb_internal_encoding() : $from_encoding, $to_encoding, $str);
}
}
 
if (!function_exists('mb_ord')) {
// https://riptutorial.com/php/example/15633/converting-unicode-characters-to-their-numeric-value-and-or-html-entities-using-php
function mb_ord($char, $encoding = 'UTF-8') {
if ($encoding === 'UCS-4BE') {
list(, $ord) = (strlen($char) === 4) ? @unpack('N', $char) : @unpack('n', $char);
return $ord;
} else {
return mb_ord(mb_convert_encoding($char, 'UCS-4BE', $encoding), 'UCS-4BE');
}
}
}
 
if (!function_exists('mb_htmlentities')) {
// https://riptutorial.com/php/example/15633/converting-unicode-characters-to-their-numeric-value-and-or-html-entities-using-php
// modified
function mb_htmlentities($string, $hex = true, $encoding = 'UTF-8') {
return preg_replace_callback('/[\x{80}-\x{10FFFF}]/u', function ($match) use ($hex, $encoding) {
$ord = (strtoupper($encoding) == 'UTF-8') ? ordUTF8($match[0]) : mb_ord($match[0]);
return sprintf($hex ? '&#x%X;' : '&#%d;', $ord);
}, $string);
}
}
/trunk/.
Property changes:
Added: svn:ignore
+.phpstan.tmp
+phpstan.neon
+phpstan.phar
+phpstan.bat