Skip to content

Commit

Permalink
Added provider field and exception handling for incorrect BIN database.
Browse files Browse the repository at this point in the history
  • Loading branch information
ip2location committed Jul 6, 2021
1 parent 760997b commit 41b8fc4
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 71 deletions.
4 changes: 4 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Revision history for ip2proxy

## 3.1.0 -- 2021-07-06

* Added provider field and exception handling for incorrect BIN database.

## 3.0.0 -- 2020-08-11

* Added support for PX9 to PX10 packages.
Expand Down
123 changes: 59 additions & 64 deletions IP2Proxy.hs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{-|
Module : IP2Proxy
Description : IP2Proxy Haskell package
Copyright : (c) IP2Location, 2020
Copyright : (c) IP2Location, 2021
License : MIT
Maintainer : sales@ip2location.com
Stability : experimental
Expand All @@ -10,7 +10,7 @@ This Haskell package allows users to query an IP address to determine if it was
IP2Proxy LITE BIN databases are available for free at http://lite.ip2location.com/
-}
module IP2Proxy (Meta, IP2ProxyRecord(..), getModuleVersion, getPackageVersion, getDatabaseVersion, open, getAll, getCountryShort, getCountryLong, getRegion, getCity, getISP, getProxyType, getDomain, getUsageType, getASN, getAS, getLastSeen, getThreat, isProxy) where
module IP2Proxy (Meta, IP2ProxyRecord(..), getModuleVersion, getPackageVersion, getDatabaseVersion, open, getAll, getCountryShort, getCountryLong, getRegion, getCity, getISP, getProxyType, getDomain, getUsageType, getASN, getAS, getLastSeen, getThreat, getProvider, isProxy) where

import qualified Data.ByteString.Lazy as BS
import qualified Data.ByteString.Lazy.Char8 as BS8
Expand All @@ -19,6 +19,7 @@ import Data.Bits
import Data.Binary.Get
import Data.IP
import Control.Exception
import System.Exit

-- | Contains proxy results.
data IP2ProxyRecord = IP2ProxyRecord {
Expand Down Expand Up @@ -46,6 +47,8 @@ data IP2ProxyRecord = IP2ProxyRecord {
last_seen :: String,
-- | Threat
threat :: String,
-- | Provider
provider :: String,
-- | Is proxy
is_proxy :: Int
} deriving (Show)
Expand Down Expand Up @@ -77,7 +80,9 @@ data Meta = Meta {
-- | IPv4 column size
ipv4columnsize :: Int,
-- | IPv6 column size
ipv6columnsize :: Int
ipv6columnsize :: Int,
-- | Wrong BIN
wrongbin :: Int
} deriving (Show)

getMeta = do
Expand All @@ -92,16 +97,27 @@ getMeta = do
ipv6databaseaddr <- getWord32le
ipv4indexbaseaddr <- getWord32le
ipv6indexbaseaddr <- getWord32le
productcode <- getWord8
producttype <- getWord8
filesize <- getWord32le

-- check if is correct BIN (should be 2 for IP2Proxy BIN file), also checking for zipped file (PK being the first 2 chars)
let wrongbin = if (productcode /= 2 && databaseyear >= 21) || (databasetype == 80 && databasecolumn == 75)
then do
1
else do
0

let ipv4columnsize = fromIntegral databasecolumn `shiftL` 2 -- 4 bytes each column
let ipv6columnsize = 16 + ((fromIntegral databasecolumn - 1) `shiftL` 2) -- 4 bytes each column, except IPFrom column which is 16 bytes
let meta = Meta (fromIntegral databasetype) (fromIntegral databasecolumn) (fromIntegral databaseyear) (fromIntegral databasemonth) (fromIntegral databaseday) (fromIntegral ipv4databasecount) (fromIntegral ipv4databaseaddr) (fromIntegral ipv6databasecount) (fromIntegral ipv6databaseaddr) (fromIntegral ipv4indexbaseaddr) (fromIntegral ipv6indexbaseaddr) ipv4columnsize ipv6columnsize
let meta = Meta (fromIntegral databasetype) (fromIntegral databasecolumn) (fromIntegral databaseyear) (fromIntegral databasemonth) (fromIntegral databaseday) (fromIntegral ipv4databasecount) (fromIntegral ipv4databaseaddr) (fromIntegral ipv6databasecount) (fromIntegral ipv6databaseaddr) (fromIntegral ipv4indexbaseaddr) (fromIntegral ipv6indexbaseaddr) ipv4columnsize ipv6columnsize wrongbin
return meta

{-|
The 'getModuleVersion' function returns a string containing the module version.
-}
getModuleVersion :: String
getModuleVersion = "3.0.0"
getModuleVersion = "3.1.0"

{-|
The 'getPackageVersion' function returns a string containing the package version.
Expand Down Expand Up @@ -134,10 +150,13 @@ ipStringToInteger = ipToInteger . read
open :: String -> IO Meta
open myfile = do
contents <- BS.readFile myfile
return $ runGet getMeta contents

readuint8 :: BS.ByteString -> Int -> Int
readuint8 contents startpos = fromIntegral (runGet getWord8 (BS.drop (fromIntegral startpos - 1) contents))
let stuff = runGet getMeta contents
let iswrong = (show (wrongbin stuff))
if iswrong == "1"
then do
die(show "Incorrect IP2Proxy BIN file format. Please make sure that you are using the latest IP2Proxy BIN file.")
else do
return $ stuff

readuint32 :: BS.ByteString -> Int -> Int
readuint32 contents startpos = fromIntegral (runGet getWord32le (BS.drop (fromIntegral startpos - 1) contents))
Expand All @@ -160,21 +179,6 @@ readstr contents startpos = do
str <- BS8.unpack (BS.take (fromIntegral len) (BS.drop (fromIntegral startpos + 1) contents))
return str

readcolcountry :: BS.ByteString -> Int -> Int -> [Int] -> (String, String)
readcolcountry contents dbtype rowoffset col = do
let x = "NOT SUPPORTED"
let [colpos] = take 1 (drop dbtype col)

if colpos == 0
then do
(x, x)
else do
let coloffset = (colpos - 1) `shiftL` 2
let x0 = readuint32 contents (rowoffset + coloffset)
let x1 = readstr contents x0
let x2 = readstr contents (x0 + 3)
(x1, x2)

readcolcountryrow :: BS.ByteString -> BS.ByteString -> Int -> [Int] -> (String, String)
readcolcountryrow contents row dbtype col = do
let x = "NOT SUPPORTED"
Expand All @@ -190,17 +194,6 @@ readcolcountryrow contents row dbtype col = do
let x2 = readstr contents (x0 + 3)
(x1, x2)

readcolstring :: BS.ByteString -> Int -> Int -> [Int] -> String
readcolstring contents dbtype rowoffset col = do
let [colpos] = take 1 (drop dbtype col)

if colpos == 0
then do
"NOT SUPPORTED"
else do
let coloffset = (colpos - 1) `shiftL` 2
readstr contents (readuint32 contents (rowoffset + coloffset))

readcolstringrow :: BS.ByteString -> BS.ByteString -> Int -> [Int] -> String
readcolstringrow contents row dbtype col = do
let [colpos] = take 1 (drop dbtype col)
Expand All @@ -217,17 +210,18 @@ countif f = length . filter f

readrecord :: BS.ByteString -> Int -> Int -> Int -> IP2ProxyRecord
readrecord contents dbtype rowoffset mode = do
let country_position = [0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3]
let region_position = [0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4]
let city_position = [0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5]
let isp_position = [0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6]
let proxytype_position = [0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2]
let domain_position = [0, 0, 0, 0, 0, 7, 7, 7, 7, 7, 7]
let usagetype_position = [0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8]
let asn_position = [0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9]
let as_position = [0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10]
let lastseen_position = [0, 0, 0, 0, 0, 0, 0, 0, 11, 11, 11]
let threat_position = [0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12]
let country_position = [0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
let region_position = [0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4]
let city_position = [0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5]
let isp_position = [0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6]
let proxytype_position = [0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
let domain_position = [0, 0, 0, 0, 0, 7, 7, 7, 7, 7, 7, 7]
let usagetype_position = [0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8]
let asn_position = [0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9]
let as_position = [0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10]
let lastseen_position = [0, 0, 0, 0, 0, 0, 0, 0, 11, 11, 11, 11]
let threat_position = [0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12]
let provider_position = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13]

let countryshort_field = 1
let countrylong_field = 2
Expand All @@ -242,73 +236,67 @@ readrecord contents dbtype rowoffset mode = do
let as_field = 1024
let lastseen_field = 2048
let threat_field = 4096
let provider_field = 8192

let allcols = (take 1 (drop dbtype country_position)) ++ (take 1 (drop dbtype region_position)) ++ (take 1 (drop dbtype city_position)) ++ (take 1 (drop dbtype isp_position)) ++ (take 1 (drop dbtype proxytype_position)) ++ (take 1 (drop dbtype domain_position)) ++ (take 1 (drop dbtype usagetype_position)) ++ (take 1 (drop dbtype asn_position)) ++ (take 1 (drop dbtype as_position)) ++ (take 1 (drop dbtype lastseen_position)) ++ (take 1 (drop dbtype threat_position))
let allcols = (take 1 (drop dbtype country_position)) ++ (take 1 (drop dbtype region_position)) ++ (take 1 (drop dbtype city_position)) ++ (take 1 (drop dbtype isp_position)) ++ (take 1 (drop dbtype proxytype_position)) ++ (take 1 (drop dbtype domain_position)) ++ (take 1 (drop dbtype usagetype_position)) ++ (take 1 (drop dbtype asn_position)) ++ (take 1 (drop dbtype as_position)) ++ (take 1 (drop dbtype lastseen_position)) ++ (take 1 (drop dbtype threat_position)) ++ (take 1 (drop dbtype provider_position))
let cols = (countif (>0) allcols) `shiftL` 2
let row = BS.take (fromIntegral cols) (BS.drop (fromIntegral rowoffset - 1) contents)

let proxy_type = if (((.&.) mode proxytype_field) /= 0) || (((.&.) mode isproxy_field) /= 0)
-- then readcolstring contents dbtype rowoffset proxytype_position
then readcolstringrow contents row dbtype proxytype_position
else ""

let (country_short, country_long) = if (((.&.) mode countryshort_field) /= 0) || (((.&.) mode countrylong_field) /= 0) || (((.&.) mode isproxy_field) /= 0)
-- then readcolcountry contents dbtype rowoffset country_position
then readcolcountryrow contents row dbtype country_position
else ("", "")

let region = if ((.&.) mode region_field) /= 0
-- then readcolstring contents dbtype rowoffset region_position
then readcolstringrow contents row dbtype region_position
else ""

let city = if ((.&.) mode city_field) /= 0
-- then readcolstring contents dbtype rowoffset city_position
then readcolstringrow contents row dbtype city_position
else ""

let isp = if ((.&.) mode isp_field) /= 0
-- then readcolstring contents dbtype rowoffset isp_position
then readcolstringrow contents row dbtype isp_position
else ""

let domain = if ((.&.) mode domain_field) /= 0
-- then readcolstring contents dbtype rowoffset domain_position
then readcolstringrow contents row dbtype domain_position
else ""

let usage_type = if ((.&.) mode usagetype_field) /= 0
-- then readcolstring contents dbtype rowoffset usagetype_position
then readcolstringrow contents row dbtype usagetype_position
else ""

let asn = if ((.&.) mode asn_field) /= 0
-- then readcolstring contents dbtype rowoffset asn_position
then readcolstringrow contents row dbtype asn_position
else ""

let as = if ((.&.) mode as_field) /= 0
-- then readcolstring contents dbtype rowoffset as_position
then readcolstringrow contents row dbtype as_position
else ""

let last_seen = if ((.&.) mode lastseen_field) /= 0
-- then readcolstring contents dbtype rowoffset lastseen_position
then readcolstringrow contents row dbtype lastseen_position
else ""

let threat = if ((.&.) mode threat_field) /= 0
-- then readcolstring contents dbtype rowoffset threat_position
then readcolstringrow contents row dbtype threat_position
else ""

let provider = if ((.&.) mode provider_field) /= 0
then readcolstringrow contents row dbtype provider_position
else ""

let is_proxy = if (country_short == "-") || (proxy_type == "-")
then 0
else if (proxy_type == "DCH") || (proxy_type == "SES")
then 2
else 1

IP2ProxyRecord country_short country_long region city isp proxy_type domain usage_type asn as last_seen threat is_proxy
IP2ProxyRecord country_short country_long region city isp proxy_type domain usage_type asn as last_seen threat provider is_proxy

searchtree :: BS.ByteString -> Integer -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> IP2ProxyRecord
searchtree contents ipnum dbtype low high baseaddr colsize iptype mode = do
Expand All @@ -330,10 +318,8 @@ searchtree contents ipnum dbtype low high baseaddr colsize iptype mode = do
then do
if iptype == 4
then
-- readrecord contents dbtype rowoffset mode
readrecord contents dbtype (rowoffset + 4) mode
else
-- readrecord contents dbtype (rowoffset + 12) mode
readrecord contents dbtype (rowoffset + 16) mode
else if ipnum < ipfrom
then
Expand All @@ -342,7 +328,7 @@ searchtree contents ipnum dbtype low high baseaddr colsize iptype mode = do
searchtree contents ipnum dbtype (mid + 1) high baseaddr colsize iptype mode
else do
let x = "INVALID IP ADDRESS"
IP2ProxyRecord x x x x x x x x x x x x (-1)
IP2ProxyRecord x x x x x x x x x x x x x (-1)

search4 :: BS.ByteString -> Integer -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> IP2ProxyRecord
search4 contents ipnum dbtype low high baseaddr indexbaseaddr colsize mode = do
Expand Down Expand Up @@ -378,7 +364,7 @@ tryfirst myIP = do
-}
getAll :: String -> Meta -> String -> IO IP2ProxyRecord
getAll myfile meta myip = do
result <- doQuery myfile meta myip 8191
result <- doQuery myfile meta myip 16383
return result

{-|
Expand Down Expand Up @@ -489,6 +475,15 @@ getThreat myfile meta myip = do
result <- doQuery myfile meta myip 4096
return (show (threat result))

{-|
The 'getProvider' function returns the provider of the proxy.
It takes 3 arguments; the BIN database file path (String), the metadata from 'open' function (Meta record) & either IPv4 or IPv6 address (String).
-}
getProvider :: String -> Meta -> String -> IO String
getProvider myfile meta myip = do
result <- doQuery myfile meta myip 8192
return (show (provider result))

{-|
The 'isProxy' function returns 0 if IP is not a proxy, 1 if is a proxy and not data center IP, 2 if is a proxy and is a data center IP, -1 if error.
It takes 3 arguments; the BIN database file path (String), the metadata from 'open' function (Meta record) & either IPv4 or IPv6 address (String).
Expand All @@ -515,7 +510,7 @@ doQuery myfile meta myip mode = do
if ipnum == -1
then do
let x = "INVALID IP ADDRESS"
return $ IP2ProxyRecord x x x x x x x x x x x x (-1)
return $ IP2ProxyRecord x x x x x x x x x x x x x (-1)
else if ipnum >= fromV4Mapped && ipnum <= toV4Mapped
then do
return $ search4 contents (ipnum - (toInteger fromV4Mapped)) (databasetype meta) 0 (ipv4databasecount meta) (ipv4databaseaddr meta) (ipv4indexbaseaddr meta) (ipv4columnsize meta) mode
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2020 IP2Location.com
Copyright (c) 2021 IP2Location.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Below are the methods supported in this package.
|Method Name|Description|
|---|---|
|open|Open the IP2Proxy BIN data for lookup.|
|getPackageVersion|Get the package version (1 to 10 for PX1 to PX10 respectively).|
|getPackageVersion|Get the package version (1 to 11 for PX1 to PX11 respectively).|
|getModuleVersion|Get the module version.|
|getDatabaseVersion|Get the database version.|
|isProxy|Check whether if an IP address was a proxy. Returned value:<ul><li>-1 : errors</li><li>0 : not a proxy</li><li>1 : a proxy</li><li>2 : a data center IP address or search engine robot</li></ul>|
Expand All @@ -35,6 +35,7 @@ Below are the methods supported in this package.
|getAS|Return the autonomous system name of the proxy.|
|getLastSeen|Return the number of days that the proxy was last seen.|
|getThreat|Return the threat type of the proxy.|
|getProvider|Return the provider of the proxy.|

## Example

Expand All @@ -43,7 +44,7 @@ import IP2Proxy

main :: IO ()
main = do
let myfile = "./IP2PROXY-IP-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN-THREAT-RESIDENTIAL.BIN"
let myfile = "./IP2PROXY-IP-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN-THREAT-RESIDENTIAL-PROVIDER.BIN"
let ip = "199.83.103.79"
meta <- open myfile

Expand All @@ -64,6 +65,7 @@ main = do
putStrLn $ "as: " ++ (show (as result))
putStrLn $ "last_seen: " ++ (show (last_seen result))
putStrLn $ "threat: " ++ (show (threat result))
putStrLn $ "provider: " ++ (show (provider result))
putStrLn $ "is_proxy: " ++ (show (is_proxy result))

result <- getCountryShort myfile meta ip
Expand All @@ -90,6 +92,8 @@ main = do
putStrLn $ "last_seen: " ++ result
result <- getThreat myfile meta ip
putStrLn $ "threat: " ++ result
result <- getProvider myfile meta ip
putStrLn $ "provider: " ++ result
result <- isProxy myfile meta ip
putStrLn $ "is_proxy: " ++ result
```
4 changes: 2 additions & 2 deletions ip2proxy.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ name: ip2proxy
-- PVP summary: +-+------- breaking API changes
-- | | +----- non-breaking API additions
-- | | | +--- code changes with no API change
version: 3.0.0
version: 3.1.0

-- A short (one-line) description of the package.
synopsis: IP2Proxy Haskell package for proxy detection.
Expand Down Expand Up @@ -60,7 +60,7 @@ library
-- other-extensions:

-- Other library packages from which modules are imported.
build-depends: base >=4.7 && <=4.14, bytestring >=0.10 && <0.11, binary >=0.8.8 && <0.9, iproute >=1.7 && <1.8
build-depends: base >=4.9 && <=4.15, bytestring >=0.10 && <0.12, binary >=0.8.4 && <0.9, iproute >=1.7 && <1.8

-- Directories containing source files.
-- hs-source-dirs:
Expand Down
Loading

0 comments on commit 41b8fc4

Please sign in to comment.