Source for file Geo.php
Documentation is available at Geo.php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// +----------------------------------------------------------------------+
// | 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 '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->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
* - 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>
* @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');
* Path to local cache file.
* Caching is compulsory to reduce load on CAIDA server
var $cache_path = "/tmp/";
* How long to wait befire rechecking cached entries in *days*
* This should be something nice and high
* 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 $default_timeout = 60;
* Location of the default netgeo server
* If port not specified, defaults to 80
var $default_caida_server = "http://netgeo.caida.org/perl/netgeo.cgi";
* Location of the default hostip server
* If port not specified, defaults to 80
var $default_hostip_server = "http://api.hostip.info/";
* Location of the localizer data file
var $localizer_data = "./demo.csv";
* Type of service to use. May be either 'caida' or 'localizer'
var $cache_prefix = "netgeo";
var $useragent = "PHP/NetGeo";
var $useragent_version = "1.0";
* How many targets can be read in at once
* Should be enough for most everyone
* Name of global var for copying $this when calling function cache
* This is needed for the cache function to operate correctly
var $netgeo_global = "netgeo_global";
* Complete User Agent string + version
* Location of the default server
* If port not specified, defaults to 80
* Value of last "target" lookup
* 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
function Net_Geo ($applicationName= "", $alternateServerUrl= "")
$this->applicationName = $applicationName;
$this->alternateServerUrl = $alternateServerUrl;
$this->cache = new Cache_Function ('file',
array ('cache_dir' => $this->cache_path,
'filename_prefix' => $this->cache_prefix
* Sets the service to use to lookup data (defaults to 'caida')
* @param string $service Service to use (caida, hostip or localizer)
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);
} elseif ($service == "caida") {
// check to see if an alternate server URL is used
if (!empty ($this->alternateServerUrl)) {
$this->default_server = $this->alternateServerUrl;
$this->default_server = $this->default_caida_server;
$this->useragent_string = sprintf("%s %s", $this->useragent,
// set the custom user agent
if (!empty ($this->applicationName)) {
$this->applicationName = trim($this->applicationName);
// also set the agent name
$this->useragent_string = sprintf("%s/%s", $this->applicationName,
} elseif ($service == "hostip") {
// check to see if an alternate server URL is used
if (!empty ($this->alternateServerUrl)) {
$this->default_server = $this->alternateServerUrl;
$this->default_server = $this->default_hostip_server;
$this->useragent_string = sprintf("%s %s", $this->useragent,
// set the custom user agent
if (!empty ($this->applicationName)) {
$this->applicationName = trim($this->applicationName);
// also set the agent name
$this->useragent_string = sprintf("%s/%s", $this->applicationName,
return new PEAR_Error ("No service specified");
$this->service = $service;
* 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
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
function getCountry ($target)
$result = $this->_execute ("getCountry", $target);
return $result["COUNTRY"];
* 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
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
* @param string $target single address
function getLat ($latLongRef)
$lat = $latLongRef["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
* @param string $target single address
function getLong ($latLongHashRef)
$long = $latLongHashRef["LONG"];
* Interface to the public functions
* @param string $methodName Lookup method
* @param mixed $target Address(es) to lookup
function _execute ($methodName, $input)
// if we haven't got a service set, then do it now
if (empty ($this->service)) {
// 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)) {
$resultArray = $this->_processArray ($methodName, $inputArray);
// if there is only one array, move the whole thing up one
if (count($resultArray) == 1 ) {
$resultArray = $resultArray[0 ];
* 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
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) {
$error = new PEAR_Error ("Too many entries. Limit is ". $this->array_limit);
// convert into a useable array
$inputArray = $this->_convertInputArray ($inputArray);
* Utility function to check what the input array
* and to convert to a correct array format for processing
* @param mixed $inputArray Address array
function _convertInputArray ($inputArray)
// first check the darn thing is actually an array
$inputArray = array ($inputArray);
// now convert to the correct array form
foreach ($inputArray as $entry) {
$returnArray[]["TARGET"] = $entry;
* 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
function _processArray ($methodName, $inputArray)
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 ) {
$url = sprintf("%s?ip=%s", $this->default_server,
$response = & $this->cache->call ($this->netgeo_global. '->_executeHttpRequest', $url);
// 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);
// parse it all into something useful
// at this point we should look for NETGEO_LIMIT_EXCEEDED as well
$dataArray[$i] = $this->_processResult ($response);
* 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
* @param array $inputArray Address(es) to lookup
function _verifyInputFormat ($inputArray)
$target = trim($inputArray["TARGET"]);
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 ];
// Bad format for input. AS number must be between 1 and 65536
} 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;
// Bad format for input. each octet in IP address must be between 0 and 255
} elseif (preg_match('/^(?:[\w\-]+\.)*[\w\-]+\.([A-Za-z]{2,3})$/', $target, $matches)) {
// 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;
// Bad TLD in domain name. 3-letter TLDs must be one of com,net,org,edu,gov,mil,in
// unrecognized format for input
* Executes a request to the netgeo server
* @param array $inputArray Address(es) to lookup
* @return string Response from netgeo server
function _executeHttpRequest ($url)
curl_setopt ($ch, CURLOPT_RETURNTRANSFER , 1 );
$response = curl_exec ($ch);
if (!isset ($urlinfo["port"])) {
$sp = @fsockopen($urlinfo["host"], $urlinfo["port"], $errno, $errstr, $this->default_timeout);
fputs($sp, "GET " . $urlinfo["path"] . "?". $urlinfo["query"] . " HTTP/1.0\r\n");
fputs($sp, "User-Agent: " . $this->useragent_string . "\r\n\r\n");
$response .= fgets($sp,128 );
* Parses the results from the server into an array
* @param string $response Response from netgeo server
function _processResult ($response)
// process the localizer result differently
// since we already have an array
if ($this->service == "localizer") {
foreach ($response as $key=> $val) {
} elseif ($this->service == "caida") {
// first check for anything icky from the server
$text = "Empty content string";
return array ("STATUS"=> $text);
$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)) {
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 ];
$retarray['RAWDATA'] = $retval;
} elseif ($this->service == "hostip") {
$options = array ( 'addDecl' => TRUE ,
'encoding' => 'ISO-8859-1',
'indentAttributes' => '_auto',
'defaultTagName' => 'members',
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 ]),
Documentation generated on Mon, 11 Mar 2019 15:40:19 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|