<?php
error_reporting(0);
class pub_yahoo_verify
{
  public $email;
  public $verifier_email;
  public $port;
  public $email_acc;
  private $errors;
  private $debug;
  private $debug_raw;

  private $_yahoo_signup_page_url = "https://login.yahoo.com/account/create?lang=en-US&src=ym&done=https%3A%2F%2Fmail.yahoo.com%2F&specId=yidregsimplified";
  private $_yahoo_signup_ajax_url = "https://login.yahoo.com/account/module/create?validateField=userId";

  private $page_content;
  private $page_headers;

  public function __construct($email = null, $verifier_email = null, $port = 25)
  {
    $this->debug = array();
    $this->debug_raw = array();
    if (!is_null($email) && !is_null($verifier_email)) {
      $this->debug[] = 'Initialized with Email: ' . $email . ', Verifier Email: ' . $verifier_email . ', Port: ' . $port;
      $this->set_email($email);
      $this->set_verifier_email($verifier_email);
    } else {
      $this->debug[] = 'Initialized with no email or verifier email values';
    }
    $this->set_port($port);
  }

  public function set_verifier_email($email)
  {
    $this->verifier_email = $email;
    $this->debug[] = 'Verifier Email was set to ' . $email;
  }

  public function get_verifier_email()
  {
    return $this->verifier_email;
  }

  public function set_email($email)
  {
    $this->email = $email;
    $this->debug[] = 'Email was set to ' . $email;
  }

  public function get_email()
  {
    return $this->email;
  }

  public function set_port($port)
  {
    $this->port = $port;
    $this->debug[] = 'Port was set to ' . $port;
  }

  public function get_port()
  {
    return $this->port;
  }

  public function get_errors()
  {
    return array('errors' => $this->errors);
  }

  public function get_debug($raw = false)
  {
    if ($raw) {
      return $this->debug_raw;
    } else {
      return $this->debug;
    }
  }

  public function verify()
  {
    $this->debug[] = 'Verify function was called.';
    $is_valid = false;
    //check if this is a yahoo email
    $is_valid = $this->validate_yahoo();
    return $is_valid;
  }

  private function getEmailAccFromEmail($email)
  {
    $email_address = explode('@', $email);

    $domain = array_shift($email_address);

    return $domain;
  }

  private function add_error($code, $msg)
  {
    $this->errors[] = array('code' => $code, 'message' => $msg);
  }

  private function clear_errors()
  {
    $this->errors = array();
  }

  private function validate_yahoo()
  {

    $this->debug[] = 'Validating a yahoo email address...';
    $this->debug[] = 'Getting the sign up page content...';
    $this->fetch_page('yahoo');

    $cookies = $this->get_cookies();
    $fields = $this->get_fields();

    $this->debug[] = 'Adding the email to fields...';

    $this->email_acc = $this->getEmailAccFromEmail($this->email);

    $fields['userId'] = $this->email_acc;

    $this->debug[] = 'Ready to submit the POST request to validate the email.';

    $response = $this->request_validation($cookies, $fields);
    $this->debug[] = 'Received response from Yahoo validation endpoint.';
    $this->debug_raw[] = array('stage' => 'yahoo_validation_response', 'response' => $response);

    $this->debug[] = 'Parsing the response...';
    $decoded_response = json_decode($response, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
      $this->debug[] = 'JSON decode error: ' . json_last_error_msg();
      $this->add_error('220', 'Invalid JSON response from Yahoo validation API.');
      return false;
    }

    $errors = [];
    if (isset($decoded_response['errors']) && is_array($decoded_response['errors'])) {
      $errors = $decoded_response['errors'];
    } elseif (isset($decoded_response['fields']['userId']['error']['id'])) {
      $errors = [['name' => 'userId', 'error' => $decoded_response['fields']['userId']['error']['id']]];
    }

    $this->debug[] = 'Searching errors for exisiting username error...';

    // If there are no errors, the identifier is available -> email does NOT exist
    if (is_array($errors) && count($errors) === 0) {
      $this->debug[] = 'Identifier available (no errors) -> Email does not exist';
      return false;
    }

    // General rule: any userId error code that starts with ERROR_ or IDENTIFIER_ means taken (exists)
    foreach ($errors as $err) {
      $name = isset($err['name']) ? $err['name'] : '';
      $code = isset($err['error']) ? $err['error'] : '';
      if ($name === 'userId' && (strpos($code, 'ERROR_') === 0 || strpos($code, 'IDENTIFIER_') === 0)) {
        $this->debug[] = 'Identifier not available (' . $code . ') -> Email exists';
        return true;
      }
    }

    // Unknown error set: be conservative and treat as non-existing
    $this->debug[] = 'Unrecognized error set, treating as not existing';
    return false;
  }

  private function fetch_page($cookies = '')
  {
    if ($cookies) {
      $opts = array(
        'http' => array(
          'method' => "GET",
          'header' => "Accept-language: en\r\n" .
            "Cookie: " . $cookies . "\r\n"
        )
      );
      $context = stream_context_create($opts);
    }

    if ($cookies) {
      $this->page_content = file_get_contents($this->_yahoo_signup_page_url, false, $context);
    } else {
      $this->page_content = file_get_contents($this->_yahoo_signup_page_url);
    }


    if ($this->page_content === false) {
      $this->debug[] = 'Could not read the sign up page.';
      $this->add_error('200', 'Cannot not load the sign up page.');
    } else {
      $this->debug[] = 'Sign up page content stored.';
      $this->debug[] = 'Getting headers...';
      $this->page_headers = $http_response_header;
      $this->debug[] = 'Sign up page headers stored.';
    }
  }

  private function get_cookies()
  {
    $this->debug[] = 'Attempting to get the cookies from the sign up page...';
    if ($this->page_content !== false) {
      $this->debug[] = 'Extracting cookies from headers...';
      $cookies = array();
      foreach ($this->page_headers as $hdr) {
        if (preg_match('/^Set-Cookie:\s*(.*?;).*?$/i', $hdr, $matches)) {
          $cookies[] = $matches[1];
        }
      }

      if (count($cookies) > 0) {
        $this->debug[] = 'Cookies found: ' . implode(' ', $cookies);
        return $cookies;
      } else {
        $this->debug[] = 'Could not find any cookies.';
      }
    }
    return false;
  }

  private function get_fields()
  {
    $dom = new DOMDocument('1.0', 'UTF-8');
    $fields = array();
    if ($this->page_content === false || $this->page_content === '') {
      $this->debug[] = 'Sign up page content empty, skipping DOM parsing.';
      $this->add_error('210', 'No HTML supplied for DOM parsing.');
      return $fields;
    }

    $libxmlPreviousState = libxml_use_internal_errors(true);
    $html = $this->page_content;
    if (strpos($html, '<meta charset=') === false) {
      $html = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . $html;
    }

    $loaded = $dom->loadHTML($html, LIBXML_NOWARNING | LIBXML_NOERROR);
    libxml_clear_errors();
    libxml_use_internal_errors($libxmlPreviousState);

    if ($loaded) {
      $this->debug[] = 'Parsing the page for input fields...';
      $xp = new DOMXpath($dom);
      $nodes = $xp->query('//input');
      foreach ($nodes as $node) {
        $fields[$node->getAttribute('name')] = $node->getAttribute('value');
      }

      $this->debug[] = 'Extracted fields.';
    } else {
      $this->debug[] = 'Something is worng with the page HTML.';
      $this->add_error('210', 'Could not load the dom HTML.');
    }

    $additional_tokens = $this->extract_additional_tokens();
    if (!empty($additional_tokens)) {
      $fields = array_merge($additional_tokens, $fields);
    }

    return $fields;
  }

  private function extract_additional_tokens()
  {
    $tokens = array();
    if ($this->page_content === false || $this->page_content === '') {
      return $tokens;
    }

    $patterns = array(
      'contextId' => '/"contextId":"([^"]+)"/m',
      'flowId' => '/"flowId":"([^"]+)"/m',
      'locale' => '/"locale":"([^"]+)"/m',
      'done' => '/"done":"([^"]*)"/m',
      'src' => '/"src":"([^"]+)"/m',
    );

    foreach ($patterns as $key => $pattern) {
      if (preg_match($pattern, $this->page_content, $match)) {
        $tokens[$key] = strip_tags(html_entity_decode($match[1]));
      }
    }

    if (empty($tokens)) {
      $tokens = array();
    }

    $ctx = $this->extract_ctx_json();
    if (!empty($ctx)) {
      if (isset($ctx['sessionIndex'])) {
        $tokens['sessionIndex'] = $ctx['sessionIndex'];
      }
      if (isset($ctx['acrumb']) && !isset($tokens['acrumb'])) {
        $tokens['acrumb'] = $ctx['acrumb'];
      }
      if (isset($ctx['crumb']) && !isset($tokens['crumb'])) {
        $tokens['crumb'] = $ctx['crumb'];
      }
      if (isset($ctx['locale']) && !isset($tokens['locale'])) {
        $tokens['locale'] = $ctx['locale'];
      }
      if (isset($ctx['done']) && !isset($tokens['done'])) {
        $tokens['done'] = $ctx['done'];
      }
      if (isset($ctx['src']) && !isset($tokens['src'])) {
        $tokens['src'] = $ctx['src'];
      }
      if (isset($ctx['contextId'])) {
        $tokens['contextId'] = $ctx['contextId'];
      }
      if (isset($ctx['flowId'])) {
        $tokens['flowId'] = $ctx['flowId'];
      }
      if (isset($ctx['context'])) {
        $tokens['context'] = $ctx['context'];
      }
      if (isset($ctx['displayName']) && $ctx['displayName'] !== '' && !isset($tokens['displayName'])) {
        $tokens['displayName'] = $ctx['displayName'];
      }
      if (isset($ctx['specData']['fields']['userId']['yidDomain']) && !isset($tokens['yidDomain'])) {
        $tokens['yidDomain'] = $ctx['specData']['fields']['userId']['yidDomain'];
      }
      if (isset($ctx['specData']['fields']['tos0']) && !isset($tokens['tos0'])) {
        $tokens['tos0'] = $ctx['specData']['fields']['tos0'];
      }
      if (isset($ctx['flowMetadata']['flowId']) && !isset($tokens['flowId'])) {
        $tokens['flowId'] = $ctx['flowMetadata']['flowId'];
      }
      if (isset($ctx['flowMetadata']['contextId']) && !isset($tokens['contextId'])) {
        $tokens['contextId'] = $ctx['flowMetadata']['contextId'];
      }
    }

    return $tokens;
  }

  private function extract_ctx_json()
  {
    if ($this->page_content === false || $this->page_content === '') {
      return array();
    }

    $pattern = '/"ctx":\{(.*?)\}\,\"urls\"/sm';
    if (!preg_match($pattern, $this->page_content, $matches)) {
      return array();
    }

    $json_fragment = '{' . $matches[1] . '}';
    $json_fragment = preg_replace('/\\"/', '"', $json_fragment);
    $json_fragment = preg_replace('/"([a-zA-Z0-9_]+)":/', '"$1":', $json_fragment);

    $decoded = json_decode($json_fragment, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
      $this->debug[] = 'Failed to decode ctx JSON: ' . json_last_error_msg();
      return array();
    }

    return $decoded;
  }

  private function request_validation($cookies, $fields)
  {

    $headers = array();
    $headers[] = "Origin: https://login.yahoo.com";
    $headers[] = "X-Requested-With: XMLHttpRequest";
    $headers[] = "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36";
    $headers[] = "Content-Type: application/x-www-form-urlencoded; charset=UTF-8";
    $headers[] = "Accept: application/json, text/plain, */*";
    $headers[] = "Referer: https://login.yahoo.com/account/create?lang=en-US&src=ym&done=https%3A%2F%2Fmail.yahoo.com%2F&specId=yidregsimplified";
    $headers[] = "Accept-Encoding: gzip, deflate, br";
    $headers[] = "Accept-Language: en-US,en;q=0.8,ar;q=0.6";

    $cookies_str = implode('; ', $cookies);
    $headers[] = "Cookie: " . $cookies_str;

    $specId = isset($fields['specId']) && $fields['specId'] !== '' ? $fields['specId'] : 'yidregsimplified';
    $crumb = isset($fields['crumb']) && $fields['crumb'] !== '' ? $fields['crumb'] : (isset($fields['acrumb']) ? $fields['acrumb'] : '');

    $firstName = isset($fields['firstName']) && $fields['firstName'] !== '' ? $fields['firstName'] : 'Tuhin';
    $lastName = isset($fields['lastName']) && $fields['lastName'] !== '' ? $fields['lastName'] : 'Bhuiyan';
    $displayName = isset($fields['displayName']) && $fields['displayName'] !== '' ? $fields['displayName'] : ($firstName . ' ' . $lastName);
    $yidDomain = isset($fields['yidDomain']) && $fields['yidDomain'] !== '' ? $fields['yidDomain'] : 'yahoo.com';
    $password = isset($fields['password']) && $fields['password'] !== '' ? $fields['password'] : 'hellonoob1991';
    $mm = isset($fields['mm']) && $fields['mm'] !== '' ? $fields['mm'] : '12';
    $dd = isset($fields['dd']) && $fields['dd'] !== '' ? $fields['dd'] : '12';
    $yyyy = isset($fields['yyyy']) && $fields['yyyy'] !== '' ? $fields['yyyy'] : '1991';

    $postFields = array(
      'lang' => isset($fields['lang']) && $fields['lang'] !== '' ? $fields['lang'] : 'en-US',
      'src' => isset($fields['src']) && $fields['src'] !== '' ? $fields['src'] : 'ym',
      'done' => 'https://mail.yahoo.com/',
      'specId' => $specId,
      'sessionIndex' => isset($fields['sessionIndex']) ? $fields['sessionIndex'] : '',
      'acrumb' => isset($fields['acrumb']) ? $fields['acrumb'] : '',
      'crumb' => $crumb,
      'displayName' => $displayName,
      'context' => isset($fields['context']) && $fields['context'] !== '' ? $fields['context'] : 'REGISTRATION',
      'googleIdToken' => isset($fields['googleIdToken']) ? $fields['googleIdToken'] : '',
      'authCode' => isset($fields['authCode']) ? $fields['authCode'] : '',
      'attrSetIndex' => isset($fields['attrSetIndex']) ? $fields['attrSetIndex'] : '0',
      'tos0' => isset($fields['tos0']) ? $fields['tos0'] : '',
      'firstName' => $firstName,
      'lastName' => $lastName,
      'yidDomain' => $yidDomain,
      'userId' => $this->email_acc,
      'password' => $password,
      'mm' => $mm,
      'dd' => $dd,
      'yyyy' => $yyyy
    );

    if (isset($fields['flowId']) && $fields['flowId'] !== '') {
      $postFields['flowId'] = $fields['flowId'];
    }
    if (isset($fields['contextId']) && $fields['contextId'] !== '') {
      $postFields['contextId'] = $fields['contextId'];
    }

    $postdata = http_build_query($postFields);
    $this->debug_raw[] = array('stage' => 'yahoo_validation_payload', 'payload' => $postFields);

    // Primary: module validate endpoint
    $primaryUrl = 'https://login.yahoo.com/account/module/create?validateField=userId';
    $this->debug_raw[] = array('stage' => 'yahoo_validation_request', 'url' => $primaryUrl);
    $ch = curl_init($primaryUrl);

    curl_setopt($ch, CURLOPT_URL, $primaryUrl);
    curl_setopt($ch, CURLOPT_POST, true);

    curl_setopt($ch, CURLOPT_ENCODING, "");
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);

    $server_output = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);


    $this->debug_raw[] = array('stage' => 'yahoo_validation_response', 'http_code' => $http_code, 'content_type' => $content_type, 'response' => is_string($server_output) ? (strlen($server_output) > 2048 ? substr($server_output, 0, 2048) . '...[truncated]' : $server_output) : $server_output);

    // Try to decode; if it fails or returns Not Found, fallback to alternate endpoint
    $decoded = json_decode($server_output, true);
    if ($server_output === 'Not Found' || json_last_error() !== JSON_ERROR_NONE || !is_array($decoded)) {
      // Fallback: older create/validate endpoint with query params
      $queryParams = array(
        'lang' => isset($fields['lang']) && $fields['lang'] !== '' ? $fields['lang'] : 'en-US',
        'src' => isset($fields['src']) && $fields['src'] !== '' ? $fields['src'] : 'ym',
        'done' => 'https://mail.yahoo.com/',
        'specId' => $specId,
      );
      $altUrl = 'https://login.yahoo.com/account/create/validate?' . http_build_query($queryParams);
      $this->debug_raw[] = array('stage' => 'yahoo_validation_request_fallback', 'url' => $altUrl);

      $ch2 = curl_init($altUrl);
      curl_setopt($ch2, CURLOPT_URL, $altUrl);
      curl_setopt($ch2, CURLOPT_POST, true);
      curl_setopt($ch2, CURLOPT_ENCODING, "");
      curl_setopt($ch2, CURLOPT_HEADER, 0);
      curl_setopt($ch2, CURLOPT_POSTFIELDS, $postdata);
      curl_setopt($ch2, CURLOPT_HTTPHEADER, $headers);
      curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch2, CURLOPT_FOLLOWLOCATION, false);
      curl_setopt($ch2, CURLOPT_SSL_VERIFYPEER, true);
      curl_setopt($ch2, CURLOPT_SSL_VERIFYHOST, 2);
      $server_output2 = curl_exec($ch2);
      $http_code2 = curl_getinfo($ch2, CURLINFO_HTTP_CODE);
      $content_type2 = curl_getinfo($ch2, CURLINFO_CONTENT_TYPE);
      $this->debug_raw[] = array('stage' => 'yahoo_validation_response_fallback', 'http_code' => $http_code2, 'content_type' => $content_type2, 'response' => is_string($server_output2) ? (strlen($server_output2) > 2048 ? substr($server_output2, 0, 2048) . '...[truncated]' : $server_output2) : $server_output2);
      return $server_output2 !== false ? $server_output2 : $server_output;
    }

    return $server_output;
  }
}