Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 6f1ddbbcd861644f28412d9deac25554804117ea 0 parents
@ozataman authored
4 .ghci
@@ -0,0 +1,4 @@
+:set -Wall
+:set -isrc
+:set -itest
+:set -XOverloadedStrings
4 .gitignore
@@ -0,0 +1,4 @@
+*.dat
+dist
+src/Data/Geolocation/GeoIP.hs
+*DS*
30 LICENSE
@@ -0,0 +1,30 @@
+Copyright (c)2011, Ozgun Ataman
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ * Neither the name of Ozgun Ataman nor the names of other
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16 README.markdown
@@ -0,0 +1,16 @@
+# hs-GeoIP: Geolocation in Haskell using MaxMind GeoIPCity
+
+This library provides fast, pure Haskell bindings to the MaxMind GeoIPCity
+database and the C library.
+
+The workflow mirrors the various MaxMind bindings to other languages with
+a flavor of Haskell introduced for safe operation.
+
+Here is an example session:
+
+ geoLookup = do
+ g <- openGeoDB "GeoLiteCity.dat" memory_cache
+ geoLocateByIPAddress g "5.5.5.5"
+
+
+Please see haddocks for additional details.
2  Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
41 hs-GeoIP.cabal
@@ -0,0 +1,41 @@
+Name: hs-GeoIP
+Version: 0.1.1
+Synopsis: Haskell bindings to the MaxMind GeoIPCity database via the C library
+License: BSD3
+License-file: LICENSE
+Author: Ozgun Ataman
+Maintainer: ozataman@gmail.com
+Category: Data
+Build-type: Simple
+Description:
+ This library provides fast, pure Haskell bindings to MaxMind's GeoIPCity
+ IP-based geolocation C API.
+
+ .
+
+ MaxMind GeoIP City database allows geo-location lookups from IP addresses.
+
+ .
+
+ To get started, you can simply download the
+ latest binary database distribution from:
+ <http://www.maxmind.com/app/geolitecity>
+
+Cabal-version: >=1.6
+extra-source-files:
+
+
+Library
+ extra-libraries: GeoIP
+ hs-source-dirs: src
+
+ build-tools: hsc2hs
+ ghc-options: -Wall
+ build-depends:
+ base >= 4
+ , bytestring >= 0.9
+ , deepseq
+
+ Exposed-modules:
+ Data.Geolocation.GeoIP
+
214 src/Data/Geolocation/GeoIP.hsc
@@ -0,0 +1,214 @@
+{-# LANGUAGE ForeignFunctionInterface, EmptyDataDecls #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE BangPatterns #-}
+
+module Data.Geolocation.GeoIP
+
+ (
+ -- * Types
+ GeoDB
+ , GeoIPOption
+ , combineOptions
+ , standard
+ , memory_cache
+ , check_cache
+ , index_cache
+ , mmap_cache
+
+ -- * Geolocation Result Type
+ , GeoIPRecord(..)
+
+ -- * Data Operations
+ , openGeoDB
+ , geoLocateByIPAddress
+ , geoLocateByIPNum
+ , mkIpNum
+ ) where
+
+-------------------------------------------------------------------------------
+import Control.Applicative
+import Control.DeepSeq
+import Data.ByteString.Char8 (ByteString, packCString, split, unpack)
+import Foreign
+import Foreign.C.String
+import Foreign.C.Types
+import Foreign.Ptr
+-------------------------------------------------------------------------------
+
+
+#include "GeoIP.h"
+#include "GeoIPCity.h"
+
+
+data GeoIP
+
+------------------------------------------------------------------------------
+-- | Type representing an established connection to a GeoIPCity database
+newtype GeoDB = GeoDB { unGeoDB :: ForeignPtr GeoIP }
+
+
+------------------------------------------------------------------------------
+-- | Return data for a geolocation lookup
+data GeoIPRecord = GeoIPRecord
+ { geoCountryCode :: !ByteString
+ , geoCountryCode3 :: !ByteString
+ , geoCountryName :: !ByteString
+ , geoRegion :: !ByteString
+ , geoCity :: !ByteString
+ , geoPostalCode :: !ByteString
+ , geoLatitude :: !Double
+ , geoLongitude :: !Double
+ , geoAreaCode :: !Int
+ , geoContinentCode :: !ByteString
+ , geoAccuracyRadius :: !Int
+ } deriving (Eq, Show)
+
+
+instance NFData GeoIPRecord where
+ rnf a = a `seq` ()
+
+
+peekGeoIPRecord :: Ptr GeoIPRecord -> IO (Maybe GeoIPRecord)
+peekGeoIPRecord p =
+ case nullPtr == p of
+ True -> return Nothing
+ False -> fmap Just r
+ where
+ !r = GeoIPRecord
+ <$> peekBS (#{peek GeoIPRecord, country_code})
+ <*> peekBS (#{peek GeoIPRecord, country_code3})
+ <*> peekBS (#{peek GeoIPRecord, country_name})
+ <*> peekBS (#{peek GeoIPRecord, region})
+ <*> peekBS (#{peek GeoIPRecord, city})
+ <*> peekBS (#{peek GeoIPRecord, postal_code})
+ <*> fmap tofloat (#{peek GeoIPRecord, latitude} p)
+ <*> fmap tofloat (#{peek GeoIPRecord, longitude} p)
+ <*> fmap toInt (#{peek GeoIPRecord, area_code} p)
+ <*> peekBS (#{peek GeoIPRecord, continent_code})
+ <*> fmap toInt (#{peek GeoIPRecord, accuracy_radius} p)
+ peekBS f = do
+ !sptr <- f p
+ case nullPtr == sptr of
+ True -> return ""
+ False -> let x = packCString sptr in x `seq` x
+ tofloat :: CFloat -> Double
+ tofloat = realToFrac
+ toInt :: CInt -> Int
+ toInt = fromIntegral
+
+
+newtype GeoIPOption = GeoIPOption { unGeoIPOpt :: CInt }
+
+
+#{enum GeoIPOption, GeoIPOption
+ , standard = GEOIP_STANDARD
+ , memory_cache = GEOIP_MEMORY_CACHE
+ , check_cache = GEOIP_CHECK_CACHE
+ , index_cache = GEOIP_INDEX_CACHE
+ , mmap_cache = GEOIP_MMAP_CACHE
+ }
+
+
+------------------------------------------------------------------------------
+-- | Collapse & combine multiple 'GeoIPOption's into one
+combineOptions :: [GeoIPOption] -> GeoIPOption
+combineOptions = GeoIPOption . foldr ((.|.) . unGeoIPOpt) 0
+
+
+------------------------------------------------------------------------------
+-- Utils
+
+
+------------------------------------------------------------------------------
+-- | Convert a string IP adress to IPNum
+mkIpNum :: ByteString -> Maybe Integer
+mkIpNum x = case valid of
+ False -> Nothing
+ True -> Just $ a * 16777216 + b * 65536 + 256 * c + d
+ where
+ valid = length parts == 4 && foldr (\x acc -> acc && x <= 255) True [a,b,c,d]
+ a : b : c : d : _ = map (read . unpack) parts
+ parts = split '.' x
+
+
+------------------------------------------------------------------------------
+-- Higher level GeoIP ops
+--
+
+
+------------------------------------------------------------------------------
+-- | Open the binary GeoIP data file with the given options.
+--
+-- This would open a file and cache in memory:
+--
+-- > openGeoDB "GeoCity.dat" memory_cache
+--
+-- The memory on the C side is automatically freed by the Haskell GC when
+-- appropriate.
+openGeoDB :: GeoIPOption -> String -> IO GeoDB
+openGeoDB ops dbname = withCString dbname $ \dbname' -> do
+ ptr <- c_GeoIP_open dbname' ops
+ GeoDB <$> newForeignPtr c_GeoIP_delete ptr
+
+
+------------------------------------------------------------------------------
+-- | Geo-locate by given IP Adress
+--
+-- > geoLocateByIPAddress db "123.123.123.123"
+geoLocateByIPAddress :: GeoDB -> ByteString -> Maybe GeoIPRecord
+geoLocateByIPAddress db ip = mkIpNum ip >>= geoLocateByIPNum db
+
+
+------------------------------------------------------------------------------
+-- | Geo-locate by given IP number. Call 'mkIpNum' on a 'String' ip address to
+-- convert to IP number.
+--
+-- > geoLocateByIPNum db 12336939327338
+geoLocateByIPNum :: GeoDB -> Integer -> Maybe GeoIPRecord
+geoLocateByIPNum (GeoDB db) ip = unsafePerformIO $ do
+ withForeignPtr db $ \db' -> do
+ ptr <- c_GeoIP_record_by_ipnum db' (fromIntegral ip)
+ rec <- peekGeoIPRecord ptr
+ return $ rec `deepseq` ()
+ case ptr == nullPtr of
+ True -> return ()
+ False -> c_GeoIPRecord_delete ptr
+ return rec
+
+
+------------------------------------------------------------------------------
+-- Low level calls into the C library
+
+foreign import ccall safe "GeoIP.h GeoIP_new"
+ c_GeoIP_new
+ :: GeoIPOption
+ -> IO (Ptr GeoIP)
+
+
+foreign import ccall safe "GeoIP.h &GeoIP_delete"
+ c_GeoIP_delete
+ :: FunPtr (Ptr GeoIP -> IO ())
+
+
+foreign import ccall safe "GeoIP.h GeoIP_open"
+ c_GeoIP_open
+ :: CString
+ -> GeoIPOption
+ -> IO (Ptr GeoIP)
+
+
+foreign import ccall safe "GeoIPCity.h GeoIP_record_by_ipnum"
+ c_GeoIP_record_by_ipnum
+ :: Ptr GeoIP
+ -> CULong
+ -> IO (Ptr GeoIPRecord)
+
+
+foreign import ccall safe "GeoIPCity.h &GeoIPRecord_delete"
+ c_GeoIPRecord_delete_funPtr
+ :: FunPtr (Ptr GeoIPRecord -> IO ())
+
+
+foreign import ccall safe "GeoIPCity.h GeoIPRecord_delete"
+ c_GeoIPRecord_delete
+ :: Ptr GeoIPRecord -> IO ()
18 test/Main.hs
@@ -0,0 +1,18 @@
+
+
+module Main where
+
+import qualified Data.ByteString.Char8 as B
+import Data.Geolocation.GeoIP
+
+
+main = simpleTest
+
+simpleTest = do
+ f <- B.lines `fmap` B.readFile "data/city_test.txt"
+ let ips = map (head . B.split '#') f
+
+ db <- openGeoDB memory_cache "data/GeoLiteCity.dat"
+ let rs = map (geoLocateByIPAddress db) ips
+ putStrLn . show $ rs
+
6 test/data/city_test.txt
@@ -0,0 +1,6 @@
+24.24.24.24 # Should return Ithaca, NY, US
+80.24.24.24 # Should return Madrid, 29, ES
+5.5.5.5
+355.455.344.233
+127.0.0.1
+192.168.1.1
30 test/hs-GeoIP-test.cabal
@@ -0,0 +1,30 @@
+Name: hs-GeoIP-test
+Version: 0.1.1
+Synopsis: Haskell bindings to the MaxMind GeoIPCity database and its C library
+License: BSD3
+License-file: LICENSE
+Author: Ozgun Ataman
+Maintainer: ozataman@gmail.com
+Category: Data
+Build-type: Simple
+
+
+Cabal-version: >=1.6
+extra-source-files:
+
+
+Executable Main
+ extra-libraries: GeoIP
+ hs-source-dirs: ./ ../src
+ main-is: Main.hs
+
+ build-tools: hsc2hs
+ ghc-options: -Wall
+ build-depends:
+ base >= 4
+ , bytestring >= 0.9
+ , deepseq
+ other-modules:
+ Data.Geolocation.GeoIP
+
+
Please sign in to comment.
Something went wrong with that request. Please try again.