hs-GeoIP (empty) → 0.1.1
raw patch · 4 files changed
+287/−0 lines, 4 filesdep +basedep +bytestringdep +deepseqsetup-changed
Dependencies added: base, bytestring, deepseq
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- hs-GeoIP.cabal +41/−0
- src/Data/Geolocation/GeoIP.hsc +214/−0
+ LICENSE view
@@ -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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hs-GeoIP.cabal view
@@ -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, idiomatic 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 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 && < 5+ , bytestring >= 0.9+ , deepseq++ Exposed-modules:+ Data.Geolocation.GeoIP+
+ src/Data/Geolocation/GeoIP.hsc view
@@ -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 ()