<?php
/*
Whois.php        PHP classes to conduct whois queries

Copyright (C)1999,2005 easyDNS Technologies Inc. & Mark Jeftovic

Maintained by David Saez

For the most recent version of this package visit:

http://www.phpwhois.org

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

//-------------------------------------------------------------------------

function generic_parser_a ($rawdata, $translate, $contacts, $main='domain', $dateformat='dmy')
{
    $blocks = generic_parser_a_blocks($rawdata, $translate, $disclaimer);

    if (isset($disclaimer) && is_array($disclaimer)) {
        $ret['disclaimer'] = $disclaimer;
    }

    if (empty($blocks) || !isset($blocks['main']) || !is_array($blocks['main'])) {
        $ret['registered'] = 'no';
        return $ret;
    }

    $r = $blocks['main'];
    $ret['registered'] = 'yes';

    foreach ($contacts as $key => $val) {
        if (isset($r[$key])) {
            $blk = is_array($r[$key]) ? $r[$key][count($r[$key]) - 1] : $r[$key];
            $blk = strtoupper(strtok($blk, ' '));
            if (isset($blocks[$blk])) $ret[$val] = $blocks[$blk];
            unset($r[$key]);
        }
    }

    if ($main) $ret[$main] = $r;

    format_dates($ret, $dateformat);
    return $ret;
}

//-------------------------------------------------------------------------

function generic_parser_a_blocks($rawdata, $translate, &$disclaimer)
{
    $r = [];
    $newblock = false;
    $hasdata = false;
    $block = [];
    $blocks = [];
    $gkey = 'main';
    $dend = false;

    foreach ($rawdata as $key => $val) {
        $val = trim($val);

        if ($val != '' && ($val[0] == '%' || $val[0] == '#')) {
            if (!$dend) $disclaimer[] = trim(substr($val, 1));
            continue;
        }

        if ($val == '') {
            $newblock = true;
            continue;
        }

        if ($newblock && $hasdata) {
            $blocks[$gkey] = $block;
            $block = [];
            $gkey = '';
        }

        $dend = true;
        $newblock = false;
        $k = trim(strtok($val, ':'));
        $v = trim(substr(strstr($val, ':'), 1));

        if ($v == '') continue;

        $hasdata = true;

        if (isset($translate[$k])) {
            $k = $translate[$k];
            if ($k == '') continue;
            if (strstr($k, '.')) {
                $block = assign($block, $k, $v);
                continue;
            }
        } else {
            $k = strtolower($k);
        }

        if ($k == 'handle') {
            $v = strtok($v, ' ');
            $gkey = strtoupper($v);
        }

        if (isset($block[$k]) && is_array($block[$k])) {
            $block[$k][] = $v;
        } else if (!isset($block[$k]) || $block[$k] == '') {
            $block[$k] = $v;
        } else {
            $x = $block[$k];
            unset($block[$k]);
            $block[$k][] = $x;
            $block[$k][] = $v;
        }
    }

    if ($hasdata) $blocks[$gkey] = $block;

    return $blocks;
}

//-------------------------------------------------------------------------

function generic_parser_b ( $rawdata, $items = false, $dateformat='mdy', $hasreg=true, $scanall=false )
{
if (!$items)
	$items = array(
				'Domain Name:' => 'domain.name',
				'Domain ID:' => 'domain.handle',
				'Sponsoring Registrar:' => 'domain.sponsor',
				'Registrar ID:' => 'domain.sponsor',
				'Domain Status:' => 'domain.status.',
				'Status:' => 'domain.status.',
				'Name Server:' => 'domain.nserver.',
				'Nameservers:' => 'domain.nserver.',
				'Maintainer:' => 'domain.referer',

				'Domain Registration Date:' => 'domain.created',
				'Domain Create Date:' => 'domain.created',
				'Domain Expiration Date:' => 'domain.expires',
				'Domain Last Updated Date:' => 'domain.changed',
				'Creation Date:' => 'domain.created',
				'Last Modification Date:' => 'domain.changed',
				'Expiration Date:' => 'domain.expires',
				'Created On:' => 'domain.created',
                'Last Updated On:' => 'domain.changed',
                'Expiration Date:' => 'domain.expires',

				'Registrant ID:' => 'owner.handle',
				'Registrant Name:' => 'owner.name',
				'Registrant Organization:' => 'owner.organization',
				'Registrant Address:' => 'owner.address.street.',
				'Registrant Address1:' => 'owner.address.street.',
				'Registrant Address2:' => 'owner.address.street.',
				'Registrant Street:' => 'owner.address.street.',
				'Registrant Street1:' => 'owner.address.street.',
				'Registrant Street2:' => 'owner.address.street.',
				'Registrant Street3:' => 'owner.address.street.',
				'Registrant Postal Code:' => 'owner.address.pcode',
				'Registrant City:' => 'owner.address.city',
				'Registrant State/Province:' => 'owner.address.state',
				'Registrant Country:' => 'owner.address.country',
				'Registrant Country/Economy:' => 'owner.address.country',
				'Registrant Phone Number:' => 'owner.phone',
				'Registrant Phone:' => 'owner.phone',
				'Registrant Facsimile Number:' => 'owner.fax',
				'Registrant FAX:' => 'owner.fax',
				'Registrant Email:' => 'owner.email',
				'Registrant E-mail:' => 'owner.email',

				'Administrative Contact ID:' => 'admin.handle',
				'Administrative Contact Name:' => 'admin.name',
				'Administrative Contact Organization:' => 'admin.organization',
				'Administrative Contact Address:' => 'admin.address.street.',
				'Administrative Contact Address1:' => 'admin.address.street.',
				'Administrative Contact Address2:' => 'admin.address.street.',
				'Administrative Contact Postal Code:' => 'admin.address.pcode',
				'Administrative Contact City:' => 'admin.address.city',
				'Administrative Contact State/Province:' => 'admin.address.state',
				'Administrative Contact Country:' => 'admin.address.country',
				'Administrative Contact Phone Number:' => 'admin.phone',
				'Administrative Contact Email:' => 'admin.email',
				'Administrative Contact Facsimile Number:' => 'admin.fax',
				'Administrative Contact Tel:' => 'admin.phone',
				'Administrative Contact Fax:' => 'admin.fax',
				'Administrative ID:' => 'admin.handle',
				'Administrative Name:' => 'admin.name',
				'Administrative Organization:' => 'admin.organization',
				'Administrative Address:' => 'admin.address.street.',
				'Administrative Address1:' => 'admin.address.street.',
				'Administrative Address2:' => 'admin.address.street.',
				'Administrative Postal Code:' => 'admin.address.pcode',
				'Administrative City:' => 'admin.address.city',
				'Administrative State/Province:' => 'admin.address.state',
				'Administrative Country/Economy:' => 'admin.address.country',
				'Administrative Phone:' => 'admin.phone',
				'Administrative E-mail:' => 'admin.email',
				'Administrative Facsimile Number:' => 'admin.fax',
				'Administrative Tel:' => 'admin.phone',
				'Administrative FAX:' => 'admin.fax',
				'Admin ID:' => 'admin.handle',
				'Admin Name:' => 'admin.name',
				'Admin Organization:' => 'admin.organization',
				'Admin Street:' => 'admin.address.street.',
				'Admin Street1:' => 'admin.address.street.',
				'Admin Street2:' => 'admin.address.street.',
				'Admin Street3:' => 'admin.address.street.',
				'Admin Address:' => 'admin.address.street.',
				'Admin Address2:' => 'admin.address.street.',
				'Admin Address3:' => 'admin.address.street.',
				'Admin City:' => 'admin.address.city',
				'Admin State/Province:' => 'admin.address.state',
				'Admin Postal Code:' => 'admin.address.pcode',
				'Admin Country:' => 'admin.address.country',
				'Admin Country/Economy:' => 'admin.address.country',
				'Admin Phone:' => 'admin.phone',
				'Admin FAX:' => 'admin.fax',
				'Admin Email:' => 'admin.email',
				'Admin E-mail:' => 'admin.email',

				'Technical Contact ID:' => 'tech.handle',
				'Technical Contact Name:' => 'tech.name',
				'Technical Contact Organization:' => 'tech.organization',
				'Technical Contact Address:' => 'tech.address.street.',
				'Technical Contact Address1:' => 'tech.address.street.',
				'Technical Contact Address2:' => 'tech.address.street.',
				'Technical Contact Postal Code:' => 'tech.address.pcode',
				'Technical Contact City:' => 'tech.address.city',
				'Technical Contact State/Province:' => 'tech.address.state',
				'Technical Contact Country:' => 'tech.address.country',
				'Technical Contact Phone Number:' => 'tech.phone',
				'Technical Contact Facsimile Number:' => 'tech.fax',
				'Technical Contact Phone:' => 'tech.phone',
				'Technical Contact Fax:' => 'tech.fax',
				'Technical Contact Email:' => 'tech.email',
				'Technical ID:' => 'tech.handle',
				'Technical Name:' => 'tech.name',
				'Technical Organization:' => 'tech.organization',
				'Technical Address:' => 'tech.address.street.',
				'Technical Address1:' => 'tech.address.street.',
				'Technical Address2:' => 'tech.address.street.',
				'Technical Postal Code:' => 'tech.address.pcode',
				'Technical City:' => 'tech.address.city',
				'Technical State/Province:' => 'tech.address.state',
				'Technical Country/Economy:' => 'tech.address.country',
				'Technical Phone Number:' => 'tech.phone',
				'Technical Facsimile Number:' => 'tech.fax',
				'Technical Phone:' => 'tech.phone',
				'Technical Fax:' => 'tech.fax',
				'Technical FAX:' => 'tech.fax',
				'Technical E-mail:' => 'tech.email',
				'Tech ID:' => 'tech.handle',
				'Tech Name:' => 'tech.name',
				'Tech Organization:' => 'tech.organization',
				'Tech Address:' => 'tech.address.street.',
				'Tech Address2:' => 'tech.address.street.',
				'Tech Address3:' => 'tech.address.street.',
				'Tech Street:' => 'tech.address.street.',
				'Tech Street1:' => 'tech.address.street.',
				'Tech Street2:' => 'tech.address.street.',
				'Tech Street3:' => 'tech.address.street.',
				'Tech City:' => 'tech.address.city',
				'Tech Postal Code:' => 'tech.address.pcode',
				'Tech State/Province:' => 'tech.address.state',
				'Tech Country:' => 'tech.address.country',
				'Tech Country/Economy:' => 'tech.address.country',
				'Tech Phone:' => 'tech.phone',
				'Tech FAX:' => 'tech.fax',
				'Tech Email:' => 'tech.email',
				'Tech E-mail:' => 'tech.email',

				'Billing Contact ID:' => 'billing.handle',
				'Billing Contact Name:' => 'billing.name',
				'Billing Contact Organization:' => 'billing.organization',
				'Billing Contact Address1:' => 'billing.address.street.',
				'Billing Contact Address2:' => 'billing.address.street.',
				'Billing Contact Postal Code:' => 'billing.address.pcode',
				'Billing Contact City:' => 'billing.address.city',
				'Billing Contact State/Province:' => 'billing.address.state',
				'Billing Contact Country:' => 'billing.address.country',
				'Billing Contact Phone Number:' => 'billing.phone',
				'Billing Contact Facsimile Number:' => 'billing.fax',
				'Billing Contact Email:' => 'billing.email',
				'Billing ID:' => 'billing.handle',
				'Billing Name:' => 'billing.name',
				'Billing Organization:' => 'billing.organization',
				'Billing Address:' => 'billing.address.street.',
				'Billing Address1:' => 'billing.address.street.',
				'Billing Address2:' => 'billing.address.street.',
				'Billing Address3:' => 'billing.address.street.',
				'Billing Street:' => 'billing.address.street.',
				'Billing Street1:' => 'billing.address.street.',
				'Billing Street2:' => 'billing.address.street.',
				'Billing Street3:' => 'billing.address.street.',
				'Billing City:' => 'billing.address.city',
				'Billing Postal Code:' => 'billing.address.pcode',
				'Billing State/Province:' => 'billing.address.state',
				'Billing Country:' => 'billing.address.country',
				'Billing Country/Economy:' => 'billing.address.country',
				'Billing Phone:' => 'billing.phone',
				'Billing Fax:' => 'billing.fax',
				'Billing FAX:' => 'billing.fax',
				'Billing Email:' => 'billing.email',
				'Billing E-mail:' => 'billing.email',

				'Zone ID:' => 'zone.handle',
				'Zone Organization:' => 'zone.organization',
				'Zone Name:' => 'zone.name',
				'Zone Address:' => 'zone.address.street.',
				'Zone Address 2:' => 'zone.address.street.',
				'Zone City:' => 'zone.address.city',
				'Zone State/Province:' => 'zone.address.state',
				'Zone Postal Code:' => 'zone.address.pcode',
				'Zone Country:' => 'zone.address.country',
				'Zone Phone Number:' => 'zone.phone',
				'Zone Fax Number:' => 'zone.fax',
				'Zone Email:' => 'zone.email'
				);

$r = [];
$disok = true;

foreach ($rawdata as $key => $val)
	{
	if (trim($val) != '')
		{
	     if (($val[0]=='%' || $val[0]=='#') && $disok)
			{
			$r['disclaimer'][] = trim(substr($val,1));
			$disok = true;
			continue;
			}

		$disok = false;
		reset($items);

		foreach ($items as $match => $field)
			{
			$pos = strpos($val,$match);

			if ($pos !== false)
				{
				if ($field != '')
					{
					$itm = trim(substr($val,$pos+strlen($match)));

					if ($itm != '')
						{
						$r = assign($r,$field,str_replace('"','\"',$itm));
						}
					}

				if (!$scanall)
					break;
				}
			}
		}
	}

if (empty($r))
	{
	if ($hasreg) $r['registered'] = 'no';
	}
else
	{
	if ($hasreg) $r['registered'] = 'yes';

	$r = format_dates($r, $dateformat);
	}

return $r;
}

//-------------------------------------------------------------------------

function assign_recursive($array, $parts, $value)
{
    $key = array_shift($parts);

    if (count($parts) == 0) {
        if (!$key) {
            $array[] = $value;
        } else {
            $array[$key] = $value;
        }
    } else {
        if (!isset($array[$key])) $array[$key] = [];
        $array[$key] = assign_recursive($array[$key], $parts, $value);
    }

    return $array;
}

//-------------------------------------------------------------------------

function assign($array, $vdef, $value)
{
    return assign_recursive($array, explode('.', $vdef), $value);
}

//-------------------------------------------------------------------------

function get_blocks ( $rawdata, $items, $partial_match = false, $def_block = false )
{

$r = array();
$endtag = '';

foreach ($rawdata as $key => $val)
	{
	$val = trim($val);
	if ($val == '') continue;

	$var = $found = false;

	foreach ($items as $field => $match)
		{
		$pos = strpos($val,$match);

		if ($field != '' && $pos !== false)
			{
			if ($val == $match)
				{
				$found = true;
				$endtag = '';
				$line = $val;
				break;
				}

			$last = substr($val,-1,1);

			if ($last == ':' || $last == '-' || $last == ']')
				{
				$found = true;
				$endtag = $last;
				$line = $val;
				}
			else
				{
				$r = assign($r, strtok($field,'#'), trim(substr($val,$pos+strlen($match))));
				}

			break;
			}
		}

	if (!$found)
		{
		if (!$var && $def_block) $r[$def_block][] = $val;
		continue;
		}

	$block = array();

	// Block found, get data ...

	foreach ($rawdata as $key => $val)
		{
		$val = trim($val);

		if ($val == '' || $val == str_repeat($val[0],strlen($val))) continue;

		$last = substr($val,-1,1);
/*
		if ($last == $endtag)
			{
			// Another block found
			prev($rawdata);
			break;
			}

		if ($endtag == '' || $partial_match)
		*/
		if ($endtag == '' || $partial_match || $last == $endtag)
			{
			//Check if this line starts another block
			$et = false;

			foreach ($items as $field => $match)
				{
				$pos = strpos($val,$match);

				if ($pos !== false && $pos == 0)
					{
					$et = true;
					break;
					}
				}

			if ($et)
				{
				// Another block found
				prev($rawdata);
				break;
				}
			}

		$block[] = $val;
		}

	if (empty($block)) continue;

	foreach ($items as $field => $match)
		{
		$pos = strpos($line,$match);

		if ($pos !== false)
			{
			$var = strtok($field,'#');
			if ($var != '')
				{
				$r = assign($r, $var, $block);
				}
			}
		}
	}

return $r;
}

//-------------------------------------------------------------------------

function easy_parser($data_str, $items, $date_format, $translate = false ,
					 $has_org = false, $partial_match = false,
					 $def_block = false )
{
$r = get_blocks($data_str, $items, $partial_match, $def_block);
$r = get_contacts($r, $translate, $has_org);
format_dates($r, $date_format);
return $r;
}

//-------------------------------------------------------------------------

function get_contacts ( $array, $extra_items='', $has_org= false )
{
if (isset($array['billing']))
	$array['billing'] = get_contact($array['billing'], $extra_items, $has_org);

if (isset($array['tech']))
	$array['tech'] = get_contact($array['tech'], $extra_items, $has_org);

if (isset($array['zone']))
	$array['zone'] = get_contact($array['zone'], $extra_items, $has_org);

if (isset($array['admin']))
	$array['admin'] = get_contact($array['admin'], $extra_items, $has_org);

if (isset($array['owner']))
	$array['owner'] = get_contact($array['owner'], $extra_items, $has_org);

if (isset($array['registrar']))
	$array['registrar'] = get_contact($array['registrar'], $extra_items, $has_org) ;

return $array;
}

//-------------------------------------------------------------------------

function get_contact ( $array, $extra_items='', $has_org= false )
{

if (!is_array($array))
	return array();

$items = array (
		'fax..:' => 'fax',
		'fax.' => 'fax',
		'fax-no:' => 'fax',
		'fax -' => 'fax',
		'fax-' => 'fax',
		'fax::'   => 'fax',
		'fax:'   => 'fax',
		'[fax]' => 'fax',
		'(fax)' => 'fax',
		'fax' => 'fax',
		'tel. ' => 'phone',
		'tel:' => 'phone',
		'phone::' => 'phone',
		'phone:' => 'phone',
		'phone-' => 'phone',
		'phone -' => 'phone',
		'email:' => 'email',
		'e-mail:' => 'email',
		'company name:' => 'organization',
		'organisation:' => 'organization',
		'first name:' => 'name.first',
		'last name:' => 'name.last',
		'street:' => 'address.street',
		'address:' => 'address.street.',
		'language:' => '',
		'location:' => 'address.city',
		'country:' => 'address.country',
		'name:' => 'name',
		'last modified:' => 'changed'
		);

if ($extra_items)
	{
	foreach($items as $match => $field)
		if (!isset($extra_items[$match]))
		$extra_items[$match] = $field;
	$items = $extra_items;
	}

foreach ($array as $key => $val)
	{
	$ok=true;

	while ($ok)
		{
		reset($items);
		$ok = false;

		foreach ($items as $match => $field)
			{
			$pos = strpos(strtolower($val),$match);

			if ($pos === false) continue;

			$itm = trim(substr($val,$pos+strlen($match)));

			if ($field != '' && $itm != '')
				{
				$r = assign($r, $field, $itm);
				}

			$val = trim(substr($val,0,$pos));

			if ($val == '')
				{
				unset($array[$key]);
				break;
				}
			else
				{
				$array[$key] = $val;
				$ok = true;
				}
			//break;
			}

		if (preg_match("/([+]*[-\(\)\. x0-9]){7,}/", $val, $matches))
			{
			$phone = trim(str_replace(' ','',$matches[0]));

			if (strlen($phone) > 8 && !preg_match('/[0-9]{5}\-[0-9]{3}/',$phone))
				{
				if (isset($r['phone']))
					{
					if (isset($r['fax'])) continue;
					$r['fax'] = trim($matches[0]);
					}
				else
					{
					$r['phone'] = trim($matches[0]);
					}

				$val = str_replace($matches[0],'',$val);

				if ($val == '')
					{
					unset($array[$key]);
					continue;
					}
				else
					{
					$array[$key] = $val;
					$ok = true;
					}
				}
			}

		if (preg_match('/([-0-9a-zA-Z._+&\/=]+@([-0-9a-zA-Z]+[.])+[a-zA-Z]{2,6})/',$val, $matches))
			{
			$r['email'] = $matches[0];

			$val = str_replace($matches[0],'',$val);
			$val = trim(str_replace('()','',$val));

			if ($val == '')
				{
				unset($array[$key]);
				continue;
				}
			else
				{
				if (!isset($r['name']))
					{
					$r['name'] = $val;
					unset($array[$key]);
					}
				else
					$array[$key] = $val;

				$ok = true;
				}
			}
		}
	}

if (!isset($r['name']) && count($array)>0)
	{
	$r['name'] = array_shift($array);
	}

if ($has_org && count($array)>0)
	{
	$r['organization'] = array_shift($array);
	}

if (isset($r['name']) && is_array($r['name']))
	{
	$r['name'] = implode($r['name'],' ');
	}

if (!empty($array))
	{
	if (isset($r['address']))
		$r['address'] = array_merge($r['address'],$array);
	else
		$r['address'] = $array;
	}

return $r;
}

//-------------------------------------------------------------------------

function format_dates(&$res, $format='mdy')
{
    if (!is_array($res)) return $res;

    foreach ($res as $key => $val) {
        if (is_array($val)) {
            if (!is_numeric($key) && ($key == 'expires' || $key == 'created' || $key == 'changed')) {
                $d = get_date($val[0], $format);
                if ($d) $res[$key] = $d;
            } else {
                $res[$key] = format_dates($val, $format);
            }
        } else {
            if (!is_numeric($key) && ($key == 'expires' || $key == 'created' || $key == 'changed')) {
                $d = get_date($val, $format);
                if ($d) $res[$key] = $d;
            }
        }
    }

    return $res;
}


//-------------------------------------------------------------------------

function get_date($date, $format)
{
    $months = [
        'jan' => 1, 'feb' => 2, 'mar' => 3, 'apr' => 4, 'may' => 5, 'jun' => 6,
        'jul' => 7, 'aug' => 8, 'sep' => 9, 'oct' => 10, 'nov' => 11, 'dec' => 12
    ];

    $date = preg_replace('/[,.\/\-]/', ' ', $date);
    $parts = explode(' ', $date);
    $res = [];

    // Date format logic
    if (strlen($parts[0]) == 8 && is_numeric($parts[0])) {
        $res['y'] = substr($parts[0], 0, 4);
        $res['m'] = substr($parts[0], 4, 2);
        $res['d'] = substr($parts[0], 6, 2);
    } else {
        // Check format structure
        $format = strtolower($format);
        foreach ($parts as $i => $val) {
            if ($val == '') continue;
            if ($format[$i] == 'y') $res['y'] = $val;
            elseif ($format[$i] == 'm') $res['m'] = $months[strtolower($val)] ?? $val;
            elseif ($format[$i] == 'd') $res['d'] = $val;
        }
    }

    if (isset($res['m']) && $res['m'] > 12) {
        list($res['d'], $res['m']) = [$res['m'], $res['d']];
    }

    $res['y'] = $res['y'] ?? date('Y');
    return sprintf("%.4d-%02d-%02d", $res['y'], $res['m'], $res['d']);
}
