Viewing file: Geo.php (24.05 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP version 4.0 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997, 1998, 1999, 2000, 2001 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.0 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Authors: Graeme Merrall <graeme@inetix.com.au> | // | Darren Ehlers <darren@ehlersconsulting.net> | // | | // +----------------------------------------------------------------------+ // // $Id: Geo.php 304669 2010-10-24 01:54:39Z clockwerx $
require_once 'PEAR.php'; require_once 'Cache/Function.php';
/** * NetGeo - determine geographic information on an internet address * * Can accept input of an AS number, an IP address or a host name * Input can be individual or an array of addresses * * $geo = new Net_Geo(); * $geo->getRecord("php.net"); * $geo->getRecord(array("php.net", "google.com")); * * Results returned are a single array of results if a string is passed in * or in the case of an array, a multi-dim array with the as the key * * Query service type (CAIDA or localizer) is not available as a constructer * to retain compatibility with existing CAIDA NetGeo classes (perl + java) * * CHANGES -- 2006-03-16 Darren Ehlers <darren@ehlersconsulting.net> * | * - Added support for the HostIP service, which still retaining the same * existing functionality (by default). To use the HostIP service, simply * add the setService() call as in the following example: * * > $geo = new Net_Geo(); * > $geo->setService('hostip'); * > $geo->getRecord("php.net"); * * - Fixed a number of minor bugs, specifically related to providing * alternate URLs. * * - Fixed code to allow changing the current service via the setService call, * without having to create a new object. * * - Added RAWDATA result array item which contains the complete returned * array data. The rest of the result array for the HostIP service is * setup to match the existing CAIDA result array. * | * CHANGES -- 2006-03-16 Darren Ehlers <darren@ehlersconsulting.net> * * @version 1.0.4 * @package NetGeo * @author Darren Ehlers <darren@ehlersconsulting.net> * @author Graeme Merrall <graeme@inetix.com.au> */
define('NETGEO_INPUT_ERROR', 'INPUT_ERROR'); define('NETGEO_HTTP_ERROR', 'HTTP_ERROR'); define('NETGEO_NO_MATCH', 'NO MATCH'); define('NETGEO_NO_COUNTRY', 'NO_COUNTRY'); define('NETGEO_LIMIT_EXCEEDED', 'NETGEO_LIMIT_EXCEEDED');
class Net_Geo {
/** * Path to local cache file. * Caching is compulsory to reduce load on CAIDA server * * @var string * @access public */ var $cache_path = "/tmp/";
/** * How long to wait befire rechecking cached entries in *days* * This should be something nice and high * * @var in * @access public */ var $cache_ttl = 30;
/** * CAIDA only * * Maximum length of time, in seconds, which will be allowed during a whois * lookup by the NetGeo server. * The actual default value is maintained by the server. * * @var int * @access public */ var $default_timeout = 60;
/** * CAIDA only * * Location of the default netgeo server * If port not specified, defaults to 80 * * @var string * @access public */ var $default_caida_server = "http://netgeo.caida.org/perl/netgeo.cgi";
/** * HostIP only * * Location of the default hostip server * If port not specified, defaults to 80 * * @var string * @access public */ var $default_hostip_server = "http://api.hostip.info/";
/** * localizer only * * Location of the localizer data file * * @var string * @access public */ var $localizer_data = "./demo.csv";
/** * Type of service to use. May be either 'caida' or 'localizer' * Default is 'caida' * * @var string @ @access private */ var $service;
/** * Cache filename prefix * * @var string * @access private */ var $cache_prefix = "netgeo";
/** * CAIDA only * * User Agent string. * * @var string * @access private */ var $useragent = "PHP/NetGeo";
/** * CAIDA only * * Class version * * @var string * @access private */ var $useragent_version = "1.0";
/** * CAIDA only * * How many targets can be read in at once * Should be enough for most everyone * * @var string * @access private */ var $array_limit = 100;
/** * Function cache object * * @var object * @access private */ var $cache;
/** * Name of global var for copying $this when calling function cache * This is needed for the cache function to operate correctly * * @var string * @access private */ var $netgeo_global = "netgeo_global";
/** * Complete User Agent string + version * * @var string * @access private */ var $useragent_string;
/** * Location of the default server * If port not specified, defaults to 80 * * @var string * @access private */ var $default_server;
/** * Value of last "target" lookup * * @var string * @access private */ var $last_target;
/** * Constructor * Both $applicationName and $alternateServerUrl are for compatibility * with the perl and java netgeo classes. * I don't guarantee to use these variables * * @param string $applicationName Application using the NetGeo class. * @param string $alternateServerUrl Alternate NetGeo server url * @return bool * @access public */ function Net_Geo($applicationName="", $alternateServerUrl="") { $this->applicationName = $applicationName; $this->alternateServerUrl = $alternateServerUrl;
// init cache object $this->cache = new Cache_Function('file', array('cache_dir' => $this->cache_path, 'filename_prefix' => $this->cache_prefix ), $this->cache_ttl * 86400 );
return true; }
/** * Sets the service to use to lookup data (defaults to 'caida') * * @param string $service Service to use (caida, hostip or localizer) * @return bool * @access public */ function setService($service = "caida") { if ($service == "localizer") {
if (@localizer_read($this->localizer_data, FALSE) == FALSE) { PEAR::raiseError("Can't read localizer data file ".$this->localizer_data); return false; }
} elseif ($service == "caida") {
// check to see if an alternate server URL is used if (!empty($this->alternateServerUrl)) { $this->default_server = $this->alternateServerUrl; } else { $this->default_server = $this->default_caida_server; }
$this->useragent_string = sprintf("%s %s", $this->useragent, $this->useragent_version );
// set the custom user agent if (!empty($this->applicationName)) { // trim whitespace $this->applicationName = trim($this->applicationName);
// also set the agent name $this->useragent_string = sprintf("%s/%s", $this->applicationName, $this->useragent ); }
} elseif ($service == "hostip") {
// check to see if an alternate server URL is used if (!empty($this->alternateServerUrl)) { $this->default_server = $this->alternateServerUrl; } else { $this->default_server = $this->default_hostip_server; }
$this->useragent_string = sprintf("%s %s", $this->useragent, $this->useragent_version );
// set the custom user agent if (!empty($this->applicationName)) { // trim whitespace $this->applicationName = trim($this->applicationName);
// also set the agent name $this->useragent_string = sprintf("%s/%s", $this->applicationName, $this->useragent ); }
} else {
// return error return new PEAR_Error("No service specified");
}
$this->service = $service; return true; }
/** * Gets a complete record for an address * Returns either a single or multidimentional arrray * if input is a string or an array respectively * * @param mixed $target Single or list of addresses * @return array * @access public */ function getRecord($target) { return $this->_execute("getRecord", $target); }
/** * Returns the 2-letter ISO 3166 country code * Returns NO_MATCH if the AS number has been looked up * but nothing was found in the whois lookups. * Returns NO_COUNTRY if the lookup returned a record * but no country could be found. * Returns an empty string if nothing was found in the database * * @param string $target single address * @return array * @access public */ function getCountry($target) { $result = $this->_execute("getCountry", $target); if (is_array($result)) { return $result["COUNTRY"]; }
return $result; }
/** * Returns an array with keys LAT, LONG, LAT_LONG_GRAN, and STATUS. * Lat/Long will be (0,0) if the target has been looked up but there was no * match in the whois lookups, or if no address could be parsed from the * whois record, or if the lat/long for the address is unknown. * Returns an empty string if nothing was found in the database * * @param string $target single address * @return array * @access public */ function getLatLong($target) { return $this->_execute("getLatLong", $target); }
/** * Included here to make the NetGeo class as similar as possible to * the NetGeoClient.java interface. * It's probably just as easy for the user to extract lat and long directly * from the array. * * @param string $target single address * @return double * @access public */ function getLat($latLongRef) { if (is_array($latLongRef)) { $lat = $latLongRef["LAT"]; } else { $lat = 0; }
return sprintf("%.2f", $lat); }
/** * Included here to make the NetGeo class as similar as possible to * the NetGeoClient.java interface. * It's probably just as easy for the user to extract lat and long directly * from the array * * @param string $target single address * @return double * @access public */ function getLong($latLongHashRef) { if (is_array($latLongHashRef)) { $long = $latLongHashRef["LONG"]; } else { $long = 0; }
return sprintf("%.2f", $long); }
/** * Interface to the public functions * * @param string $methodName Lookup method * @param mixed $target Address(es) to lookup * @return array * @access private */ function _execute($methodName, $input) { // if we haven't got a service set, then do it now if (empty($this->service)) { $this->setService(); }
// Test the target strings in the input array. Any targets not in // an acceptable format will have their STATUS field set to INPUT_ERROR. // This method will also store the standardized target into the array // for use as a key in the cache table. $inputArray = $this->_verifyInputFormatArray($methodName, $input); if (PEAR::isError($inputArray)) { return $inputArray; }
$resultArray = $this->_processArray($methodName, $inputArray);
// if there is only one array, move the whole thing up one if (count($resultArray) == 1) { $resultArray = $resultArray[0]; }
return $resultArray; }
/** * Verify the type of the target argument and verify types of array elements * Also converts the input array into the start of the output array * * @param string $methodName Lookup method * @param mixed $inputArray Address(es) to lookup * @return array or pear error object on failure * @access private */ function _verifyInputFormatArray($methodName, $inputArray) { // makes sure that the input is an array // if length is > than ARRAY_LIMIT_LENTH then bomb ou if (count($inputArray) > $this->array_limit) { // raise an error $error = new PEAR_Error("Too many entries. Limit is ".$this->array_limit); return $error; }
// convert into a useable array $inputArray = $this->_convertInputArray($inputArray); return $inputArray; }
/** * Utility function to check what the input array * and to convert to a correct array format for processing * * @param mixed $inputArray Address array * @return array * @access private */ function _convertInputArray($inputArray) { // first check the darn thing is actually an array if (!is_array($inputArray)) { $inputArray = array($inputArray); }
// now convert to the correct array form foreach ($inputArray as $entry) { $returnArray[]["TARGET"] = $entry; }
return $returnArray; }
/** * Main function that processes addresses * * It might be a good idea to move the caching up one level? * * @param string $methodName Lookup method * @param array $inputArray Formatted address array * @return array * @access private */ function _processArray($methodName, $inputArray) { $i = 0; foreach ($inputArray as $entry) { $entry = $this->_verifyInputFormat($entry);
if (isset($entry["TARGET"]) && !isset($entry["INPUT_ERROR"])) {
$this->last_target = $entry["TARGET"];
// set up the cache work around $GLOBALS[$this->netgeo_global] =& $this;
if ($this->service == "localizer") {
$response = $this->cache->call('localizer_search', $entry["TARGET"]);
} elseif ($this->service == 'hostip') {
if (ip2long($entry["TARGET"]) === false) {
$ip = gethostbyname($entry["TARGET"]); } else {
$ip = $entry["TARGET"]; }
$url = sprintf("%s?ip=%s", $this->default_server, $ip );
$response =& $this->cache->call($this->netgeo_global.'->_executeHttpRequest', $url);
} else {
// else do the HTTP request $url = sprintf("%s?method=%s&target=%s", $this->default_server, $methodName, $entry["TARGET"] );
$response =& $this->cache->call($this->netgeo_global.'->_executeHttpRequest', $url);
}
if (!isset($response)) { $entry["STATUS"] = NETGEO_HTTP_ERROR; }
// parse it all into something useful // at this point we should look for NETGEO_LIMIT_EXCEEDED as well $dataArray[$i] = $this->_processResult($response);
} else { $dataArray[$i] = $entry; }
$i++; }
if (is_array($dataArray)) { return $dataArray; } else { return array("STATUS"=>NETGEO_HTTP_ERROR); } }
/** * Test the input and make sure it is in an acceptable format. The input * can be an AS number (with or without a leading "AS"), an IP address in * dotted decimal format, or a domain name. Stores the standardized targe * string into the hash if input target is valid format, otherwise stores * undef into hash. * * @param array $inputArray Address(es) to lookup * @return array * @access private */ function _verifyInputFormat($inputArray) { $target = trim($inputArray["TARGET"]);
// look for AS|as if (preg_match('/^(?:AS|as)?\s?(\d{1,})$/', $target, $matches)) {
// check the AS number. Btwn 1 and 65536 if ($matches[1] >= 1 && $matches[1] < 65536) { $standardizedTarget = $matches[0]; } else { $inputArray["INPUT_ERROR"] = NETGEO_INPUT_ERROR; // raise some error tex // Bad format for input. AS number must be between 1 and 65536 return $inputArray; }
// IP number } elseif (preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $target, $matches)) {
if ($matches[1] <= 255 && $matches[2] <= 255 && $matches[3] <= 255 && $matches[4] <= 255) { $standardizedTarget = $target; } else { $inputArray["INPUT_ERROR"] = NETGEO_INPUT_ERROR; // raise some error tex // Bad format for input. each octet in IP address must be between 0 and 255 return $inputArray; }
// TLD } elseif (preg_match('/^(?:[\w\-]+\.)*[\w\-]+\.([A-Za-z]{2,3})$/', $target, $matches)) {
$tld = $matches[1];
// TLD length is either 2 or 3. If length is 2 we just accept it, // otherwise we test the TLD against the list. if (strlen($tld) == 2 || preg_match('/^(com|net|org|edu|gov|mil|int)/i', $tld)) { $standardizedTarget = $target; } else { $inputArray["INPUT_ERROR"] = NETGEO_INPUT_ERROR; // raise some error tex // Bad TLD in domain name. 3-letter TLDs must be one of com,net,org,edu,gov,mil,in return $inputArray; }
} else {
$inputArray["INPUT_ERROR"] = NETGEO_INPUT_ERROR; // raise some error text // unrecognized format for input return $inputArray;
}
return $inputArray; }
/** * Executes a request to the netgeo server * * @param array $inputArray Address(es) to lookup * @return string Response from netgeo server * @access private */ function _executeHttpRequest($url) { $response = "";
if (function_exists('curl_init')) { $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($ch); curl_close($ch); } else { // split the server url $urlinfo = parse_url($url); if (!isset($urlinfo["port"])) { $urlinfo["port"] = 80; }
$sp = @fsockopen($urlinfo["host"], $urlinfo["port"], $errno, $errstr, $this->default_timeout); if (!$sp) { return false; }
fputs($sp, "GET " . $urlinfo["path"] ."?". $urlinfo["query"] . " HTTP/1.0\r\n"); fputs($sp, "User-Agent: " . $this->useragent_string . "\r\n\r\n"); while (!feof($sp)) { $response .= fgets($sp,128); } fclose ($sp); }
return $response; }
/** * Parses the results from the server into an array * * @param string $response Response from netgeo server * @return array * @access private */ function _processResult($response) { // process the localizer result differently // since we already have an array if ($this->service == "localizer") {
foreach ($response as $key=>$val) {
$retarray[strtoupper($key)] = $val; }
} elseif ($this->service == "caida") {
$lineArray = preg_split("/\n/", $response); $line = array_shift($lineArray);
// first check for anything icky from the server if (preg_match("/".NETGEO_HTTP_ERROR."/", $line) || preg_match('/^\s*$/', $response)) {
// empty empty empty if (preg_match('/^\s*$/', $text)) { $text = "Empty content string"; return array("STATUS"=>$text); }
} elseif (preg_match("/".NETGEO_LIMIT_EXCEEDED."/", $line)) { $text = 'Query limit exceeded'; return array("STATUS"=>$text); }
// now loop through. This should being us out at TARGET while (isset($line) && !preg_match("/^TARGET:/", $line)) { $line = array_shift($lineArray); }
// keep going while (isset($line)) { if (preg_match("/^TARGET:\s+(.*\S)\s*<br>/", $line, $matches)) { $retval["TARGET"] = $matches[1]; } elseif (preg_match("/^STATUS:\s+([\w\s]+\S)\s*<br>/", $line, $matches)) { $retval["STATUS"] = $matches[1]; } elseif (preg_match("/^(\w+):\s+(.*\S)\s*<br>/", $line, $matches)) { $retval[$matches[1]] = $matches[2]; } $line = array_shift($lineArray); }
$retarray = $retval; $retarray['RAWDATA'] = $retval;
} elseif ($this->service == "hostip") {
$options = array( 'addDecl' => TRUE, 'encoding' => 'ISO-8859-1', 'indent' => ' ', 'indentAttributes' => '_auto', 'rootName' => __CLASS__, 'defaultTagName' => 'members', 'typeHints' => TRUE );
require_once 'XML/Unserializer.php'; $hUnserializer = new XML_Unserializer($options);
$status = $hUnserializer->unserialize($response); if (PEAR::isError($status)) { return array("STATUS" => $status->getMessage()); }
$retval = $hUnserializer->getUnserializedData();
$cityState = explode(',', $retval['gml:featureMember']['Hostip']['gml:name']); $latLong = explode(',', $retval['gml:featureMember']['Hostip']['ipLocation']['gml:PointProperty']['gml:Point']['gml:coordinates']);
$retarray = array('TARGET' => $this->last_target, 'CITY' => trim($cityState[0]), 'STATE' => trim($cityState[1]), 'COUNTRY' => trim($retval['gml:featureMember']['Hostip']['countryAbbrev']), 'LAT' => trim($latLong[1]), 'LONG' => trim($latLong[0]), 'STATUS' => 'OK', 'RAWDATA' => $retval ); }
return $retarray;
}
}
?>
|