mirror of https://github.com/PurpleI2P/regi2p.git
Domain registry project
http://reg.i2p/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
245 lines
7.1 KiB
245 lines
7.1 KiB
<?php |
|
|
|
namespace App; |
|
|
|
class Utils { |
|
// I2P uses custom base64 alphabet, defining translation here |
|
private const b64Trans = array("-" => "+", "~" => "/"); |
|
|
|
// base32 alpabet |
|
private const b32alphabet = 'abcdefghijklmnopqrstuvwxyz234567'; |
|
|
|
/** |
|
* base32 encoding function. |
|
* @param string $data |
|
* @return string |
|
*/ |
|
private static function b32encode(string $data): string { |
|
if (empty($data)) { |
|
return ""; |
|
} |
|
|
|
/* Create binary string zeropadded to eight bits. */ |
|
$data = str_split($data); |
|
$binary = implode("", array_map(function ($character) { |
|
return sprintf("%08b", ord($character)); |
|
}, $data)); |
|
|
|
/* Split to five bit chunks and make sure last chunk has five bits. */ |
|
$binary = str_split($binary, 5); |
|
$last = array_pop($binary); |
|
if (null !== $last) { |
|
$binary[] = str_pad($last, 5, "0", STR_PAD_RIGHT); |
|
} |
|
|
|
/* Convert each five bits to Base32 character. */ |
|
$encoded = implode("", array_map(function ($fivebits) { |
|
$index = bindec($fivebits); |
|
return self::b32alphabet[$index]; |
|
}, $binary)); |
|
|
|
return $encoded; |
|
} |
|
|
|
/** |
|
* I2P base64 decoding function. |
|
* @param string $data |
|
* @return string |
|
*/ |
|
private static function b64decode(string $data): string { |
|
return base64_decode(strtr($data, static::b64Trans), true); |
|
} |
|
|
|
/** |
|
* Checks if the given domain is in Punycode. |
|
* @param string $domain The domain to check. |
|
* @return bool Whether the domain is in Punycode. |
|
*/ |
|
public static function isPunycodeDomain($domain): bool |
|
{ |
|
$hasPunycode = false; |
|
|
|
foreach (explode('.', $domain) as $part) { |
|
if (false === static::isAscii($part)) |
|
return false; |
|
|
|
if (static::isPunycode($part)) |
|
$hasPunycode = true; |
|
} |
|
|
|
return $hasPunycode; |
|
} |
|
|
|
/** |
|
* Checks if the given value is in ASCII character encoding. |
|
* @param string $value The value to check. |
|
* @return bool Whether the value is in ASCII character encoding. |
|
*/ |
|
public static function isAscii($value): bool { |
|
return ('ASCII' === mb_detect_encoding($value, 'ASCII', true)); |
|
} |
|
|
|
/** |
|
* Checks if the given value is in Punycode. |
|
* @param string $value The value to check. |
|
* @return bool Whether the value is in Punycode. |
|
* @throws \LogicException If the string is not encoded by UTF-8. |
|
*/ |
|
public static function isPunycode($value): bool |
|
{ |
|
if (false === static::isAscii($value)) |
|
return false; |
|
|
|
if ('UTF-8' !== mb_detect_encoding($value, 'UTF-8', true)) |
|
throw new \LogicException('The string should be encoded by UTF-8 to do the right check.'); |
|
|
|
return (0 === mb_stripos($value, 'xn--', 0, 'UTF-8')); |
|
} |
|
|
|
/** |
|
* base64 validation function. |
|
* @param string $data |
|
* @return bool |
|
*/ |
|
public static function isValidBase64(string $data): bool { |
|
$len = strlen($data); |
|
|
|
if($len < 516 || $len > 616) { |
|
return false; |
|
} |
|
|
|
/* .i2p in string */ |
|
if(preg_match('/\.i2p/', $data)) { |
|
return false; |
|
} |
|
|
|
/* DSA-SHA1. Will reject them because they are old (length 516) */ |
|
if($len == 516 && preg_match('/^[a-zA-Z0-9\-~]+AA$/', $data)) { |
|
return false; |
|
} |
|
|
|
/* ECDSA or EdDSA */ |
|
if($len == 524 && !preg_match('/^[a-zA-Z0-9\-~]+AEAA[Ec]AAA==$/', $data)) { |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
/** |
|
* I2P base64 to base32 conversion function. |
|
* @param string $data |
|
* @return string |
|
*/ |
|
public static function b32from64(string $data): string { |
|
return self::b32encode(hash('sha256', self::b64decode($data), true)); |
|
} |
|
|
|
/** |
|
* Domain validation function. |
|
* @param string $data |
|
* @return bool |
|
*/ |
|
public static function isValidDomain(string $data, string &$result): bool { |
|
$len = strlen($data); |
|
|
|
if($len < 5 || $len > 255) { // 255 max: rfc2181, section 11 |
|
$result = "Domain must be longer than 5 and lesser than 255 chars."; |
|
return false; |
|
} |
|
|
|
if(preg_match('/(?:^b32|\.b32)\.i2p$/', $data)) { |
|
$result = "Domain can't be b32.i2p or end with .b32.i2p."; |
|
return false; |
|
} |
|
|
|
if(preg_match('/(?:^console|\.console)\.i2p$/', $data)) { |
|
$result = "Domain can't be console.i2p or end with .console.i2p."; |
|
return false; |
|
} |
|
|
|
if(preg_match('/(?:^proxy|\.proxy)\.i2p$/', $data)) { |
|
$result = "Domain can't be proxy.i2p or end with .proxy.i2p."; |
|
return false; |
|
} |
|
|
|
if(preg_match('/(?:^router|\.router)\.i2p$/', $data)) { |
|
$result = "Domain can't be router.i2p or end with .router.i2p."; |
|
return false; |
|
} |
|
|
|
if(filter_var($data, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) === false || !preg_match('/\.i2p$/', $data)) { |
|
$result = "Domain is not valid. The label must be less than 63 characters long, contain only alphanumerics or hyphens and end with .i2p."; |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
public static function verifyHostRecord(string $data, &$output): bool { |
|
$retval = null; |
|
|
|
if (strpos($data, "&") !== false) |
|
return false; // registration string can't have ampersands |
|
|
|
$cmd = dirname(__FILE__) . '/../bin/verifyhost ' . escapeshellarg($data); |
|
exec($cmd, $output, $retval); |
|
|
|
if (!$retval) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
public static function parseHostRecord(string $data): array { |
|
$record = []; |
|
|
|
/* Return empty array if no data provided */ |
|
if(!strlen($data)) |
|
return []; |
|
|
|
$data = trim($data); |
|
|
|
/* Split addressbook host record and extended commands */ |
|
$tmp = preg_split("/#!/", $data); |
|
|
|
/* Parse host record */ |
|
$host = preg_split("/\=/", $tmp[0], 2); |
|
|
|
/* Return empty array if host or b64 is not set */ |
|
if(!isset($host[0]) || !isset($host[1])) |
|
return []; |
|
|
|
/* Return if base64 can't be decoded */ |
|
if(self::b64decode($host[1]) === false) |
|
return []; |
|
|
|
$record["host"] = $host[0]; |
|
$record["b64"] = $host[1]; |
|
|
|
/* Parse extended commands */ |
|
if(isset($tmp[1])) { |
|
$cmds = []; |
|
|
|
$cmdlist = preg_split("/#/", $tmp[1]); |
|
foreach($cmdlist as $i) { |
|
list($name, $value) = explode('=', $i, 2); |
|
$cmds[$name] = $value; |
|
} |
|
|
|
$record["commands"] = $cmds; |
|
} |
|
|
|
return $record; |
|
} |
|
|
|
public static function getResponseHeader($header, $response) { |
|
foreach ($response as $key => $r) { |
|
// Match the header name up to ':', compare lower case |
|
if (stripos($r, $header . ':') === 0) { |
|
list($headername, $headervalue) = explode(":", $r, 2); |
|
return trim($headervalue); |
|
} |
|
} |
|
} |
|
}
|
|
|