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

<?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);
}
}
}
}