1 {-# LANGUAGE OverloadedStrings #-}
3 module NmcDom ( NmcDom(..)
9 import Prelude hiding (length)
10 import Data.ByteString.Lazy (ByteString)
11 import qualified Data.Text as T (unpack)
12 import Data.List.Split
14 import Data.Map as M (Map, lookup, delete, size)
15 import Data.Vector (toList,(!),length)
16 import Control.Applicative ((<$>), (<*>), empty)
19 data NmcRRService = NmcRRService
28 instance FromJSON NmcRRService where
30 if length a == 6 then NmcRRService
40 data NmcRRI2p = NmcRRI2p
41 { i2pDestination :: String
46 instance FromJSON NmcRRI2p where
47 parseJSON (Object o) = NmcRRI2p
48 <$> o .: "destination"
53 data NmcDom = NmcDom { domService :: Maybe [NmcRRService]
54 , domIp :: Maybe [String]
55 , domIp6 :: Maybe [String]
56 , domTor :: Maybe String
57 , domI2p :: Maybe NmcRRI2p
58 , domFreenet :: Maybe String
59 , domAlias :: Maybe String
60 , domTranslate :: Maybe String
61 , domEmail :: Maybe String
62 , domLoc :: Maybe String
63 , domInfo :: Maybe Value
64 , domNs :: Maybe [String]
65 , domDelegate :: Maybe [String]
66 , domImport :: Maybe String
67 , domMap :: Maybe (Map String NmcDom)
68 , domFingerprint :: Maybe [String]
69 , domTls :: Maybe (Map String
70 (Map String [[String]]))
71 , domDs :: Maybe [[String]]
74 instance FromJSON NmcDom where
75 -- Wherever we expect a domain object, there may be a string
76 -- containing IPv4 address. Interpret it as such.
77 -- Question: shall we try to recognize IPv6 addresses too?
78 parseJSON (String s) =
80 then emptyNmcDom { domIp = Just [s'] }
84 isIPv4 x = all isNibble $ splitOn "." x
86 if all isDigit x then (read x :: Int) < 256
88 parseJSON (Object o) = NmcDom
104 <*> o .:? "fingerprint"
109 emptyNmcDom = NmcDom Nothing Nothing Nothing Nothing Nothing Nothing
110 Nothing Nothing Nothing Nothing Nothing Nothing
111 Nothing Nothing Nothing Nothing Nothing Nothing
113 -- FIXME -- I hope there exists a better way to merge records!
114 mergeNmcDom :: NmcDom -> NmcDom -> NmcDom
115 mergeNmcDom sub dom = dom { domService = choose domService
116 , domIp = choose domIp
117 , domIp6 = choose domIp6
118 , domTor = choose domTor
119 , domI2p = choose domI2p
120 , domFreenet = choose domFreenet
121 , domAlias = choose domAlias
122 , domTranslate = choose domTranslate
123 , domEmail = choose domEmail
124 , domLoc = choose domLoc
125 , domInfo = choose domInfo
126 , domNs = choose domNs
127 , domDelegate = choose domDelegate
128 , domImport = choose domImport
129 , domMap = choose domMap
130 , domFingerprint = choose domFingerprint
131 , domTls = choose domTls
132 , domDs = choose domDs
135 choose :: (NmcDom -> Maybe a) -> Maybe a
136 choose field = case field dom of
140 -- | Perform query and return error string or parsed domain object
142 (String -> IO (Either String ByteString)) -- ^ query operation action
144 -> IO (Either String NmcDom) -- ^ error string or domain
145 queryNmcDom queryOp key = do
148 Left estr -> return $ Left estr
149 Right str -> case decode str :: Maybe NmcDom of
150 Nothing -> return $ Left $ "Unparseable value: " ++ (show str)
151 Just dom -> return $ Right dom
153 -- | Try to fetch "import" object and merge it into the base domain
154 -- Original "import" element is removed, but new imports from the
155 -- imported objects are processed recursively until there are none.
157 (String -> IO (Either String ByteString)) -- ^ query operation action
158 -> NmcDom -- ^ base domain
159 -> IO (Either String NmcDom) -- ^ result with merged import
160 mergeImport queryOp base = do
162 mbase = mergeSelf base
163 base' = mbase {domImport = Nothing}
165 case domImport mbase of
166 Nothing -> return $ Right base'
168 sub <- queryNmcDom queryOp key
170 Left e -> return $ Left e
171 Right sub' -> mergeImport queryOp $ sub' `mergeNmcDom` base'
173 -- | If there is an element in the map with key "", merge the contents
174 -- and remove this element. Do this recursively.
175 mergeSelf :: NmcDom -> NmcDom
179 base' = base {domMap = removeSelf map}
180 removeSelf Nothing = Nothing
181 removeSelf (Just map) = if size map' == 0 then Nothing else Just map'
182 where map' = M.delete "" map
187 case M.lookup "" map' of
189 Just sub -> (mergeSelf sub) `mergeNmcDom` base'
191 -- | Presence of some elements require removal of some others
192 normalizeDom :: NmcDom -> NmcDom
193 normalizeDom dom = foldr id dom [ translateNormalizer
194 -- , nsNormalizer -- FIXME retrun this
197 nsNormalizer dom = case domNs dom of
199 Just ns -> emptyNmcDom { domNs = domNs dom, domEmail = domEmail dom }
200 translateNormalizer dom = case domTranslate dom of
202 Just tr -> dom { domMap = Nothing }
204 -- | Merge imports and Selfs and follow the maps tree to get dom
206 (String -> IO (Either String ByteString)) -- ^ query operation action
207 -> [String] -- ^ subdomain chain
208 -> NmcDom -- ^ base domain
209 -> IO (Either String NmcDom) -- ^ fully processed result
210 descendNmcDom queryOp subdom base = do
211 base' <- mergeImport queryOp base
213 [] -> return $ fmap normalizeDom base'
216 Left err -> return base'
218 case domMap base'' of
219 Nothing -> return $ Right emptyNmcDom
221 case M.lookup d map of
222 Nothing -> return $ Right emptyNmcDom
223 Just sub -> descendNmcDom queryOp ds sub
225 -- | Initial NmcDom populated with "import" only, suitable for "descend"
227 String -- ^ domain key (without namespace prefix)
228 -> NmcDom -- ^ resulting seed domain
229 seedNmcDom dn = emptyNmcDom { domImport = Just ("d/" ++ dn)}