1<?php 2 3namespace Iodev\Whois\Loaders; 4 5use Iodev\Whois\Exceptions\ConnectionException; 6use Iodev\Whois\Exceptions\WhoisException; 7use Iodev\Whois\Helpers\TextHelper; 8 9class SocketLoader implements ILoader 10{ 11 public function __construct($timeout = 60) 12 { 13 $this->setTimeout($timeout); 14 } 15 16 /** @var int */ 17 private $timeout; 18 19 /** @var string|bool */ 20 private $origEnv = false; 21 22 /** 23 * @return int 24 */ 25 public function getTimeout() 26 { 27 return $this->timeout; 28 } 29 30 /** 31 * @param int $seconds 32 * @return $this 33 */ 34 public function setTimeout($seconds) 35 { 36 $this->timeout = max(0, (int)$seconds); 37 return $this; 38 } 39 40 /** 41 * @param string $whoisHost 42 * @param string $query 43 * @return string 44 * @throws ConnectionException 45 * @throws WhoisException 46 */ 47 public function loadText($whoisHost, $query) 48 { 49 $this->setupEnv(); 50 if (!gethostbynamel($whoisHost)) { 51 $this->teardownEnv(); 52 throw new ConnectionException("Host is unreachable: $whoisHost"); 53 } 54 $this->teardownEnv(); 55 $errno = null; 56 $errstr = null; 57 $handle = @fsockopen($whoisHost, 43, $errno, $errstr, $this->timeout); 58 if (!$handle) { 59 throw new ConnectionException($errstr, $errno); 60 } 61 if (false === fwrite($handle, $query)) { 62 throw new ConnectionException("Query cannot be written"); 63 } 64 $text = ""; 65 while (!feof($handle)) { 66 $chunk = fread($handle, 8192); 67 if (false === $chunk) { 68 throw new ConnectionException("Response chunk cannot be read"); 69 } 70 $text .= $chunk; 71 } 72 fclose($handle); 73 74 return $this->validateResponse(TextHelper::toUtf8($text)); 75 } 76 77 /** 78 * @param string $text 79 * @return mixed 80 * @throws WhoisException 81 */ 82 private function validateResponse($text) 83 { 84 if (preg_match('~^WHOIS\s+.*?LIMIT\s+EXCEEDED~ui', $text, $m)) { 85 throw new WhoisException($m[0]); 86 } 87 return $text; 88 } 89 90 /** 91 * 92 */ 93 private function setupEnv() 94 { 95 $this->origEnv = getenv('RES_OPTIONS'); 96 putenv("RES_OPTIONS=retrans:1 retry:1 timeout:{$this->timeout} attempts:1"); 97 } 98 99 /** 100 * 101 */ 102 private function teardownEnv() 103 { 104 $this->origEnv === false ? putenv("RES_OPTIONS") : putenv("RES_OPTIONS={$this->origEnv}"); 105 } 106} 107