/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"ed-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', '<\\1', $str); |
foreach ($forbidden_tags as $tagname) { |
if ($tagname == '*') { |
$str = str_replace('<', '<', $str); |
} else { |
$str = preg_replace('@<(/{0,1}'.preg_quote($tagname,'@').'[^a-zA-Z])@i', '<\\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', 'O\\1', $str); |
$str = preg_replace('@o([nN][a-zA-Z]+\s*=)@m', 'o\\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
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(' ', ' ', $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 == '<') $c = '<'; |
if ($c == '>') $c = '>'; |
if ($c == '=') $c = '='; |
if ($c == '"') $c = '"'; |
if ($c == ''') $c = '\''; |
if ($c == '&') $c = '&'; // DM 24.08.2016 Re-added because OIDplus 1.0 XML export |
} |
if (!$encode_linebreaks) { |
if ($allow_html) { |
if ($c == " ") $c = "<br />"; |
if ($c == " ") $c = "<br />"; |
} else { |
if ($c == " ") $c = "\n"; |
if ($c == " ") $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 |