1 {-# LANGUAGE OverloadedStrings #-}
3 module NmcDom ( NmcDom(..)
9 import Data.ByteString.Lazy (ByteString)
10 import qualified Data.Text as T (unpack)
11 import Data.List.Split
13 import Data.Map as M (Map, lookup, delete, size)
14 import Control.Applicative ((<$>), (<*>), empty)
17 data NmcRRService = NmcRRService -- unused
26 instance FromJSON NmcRRService where
27 parseJSON (Object o) = NmcRRService
36 data NmcRRI2p = NmcRRI2p
37 { i2pDestination :: String
42 instance FromJSON NmcRRI2p where
43 parseJSON (Object o) = NmcRRI2p
44 <$> o .: "destination"
49 data NmcDom = NmcDom { domService :: Maybe [[String]] -- [NmcRRService]
50 , domIp :: Maybe [String]
51 , domIp6 :: Maybe [String]
52 , domTor :: Maybe String
53 , domI2p :: Maybe NmcRRI2p
54 , domFreenet :: Maybe String
55 , domAlias :: Maybe String
56 , domTranslate :: Maybe String
57 , domEmail :: Maybe String
58 , domLoc :: Maybe String
59 , domInfo :: Maybe Value
60 , domNs :: Maybe [String]
61 , domDelegate :: Maybe [String]
62 , domImport :: Maybe String
63 , domMap :: Maybe (Map String NmcDom)
64 , domFingerprint :: Maybe [String]
65 , domTls :: Maybe (Map String
66 (Map String [[String]]))
67 , domDs :: Maybe [[String]]
70 instance FromJSON NmcDom where
71 -- Wherever we expect a domain object, there may be a string
72 -- containing IPv4 address. Interpret it as such.
73 -- Question: shall we try to recognize IPv6 addresses too?
74 parseJSON (String s) =
76 then emptyNmcDom { domIp = Just [s'] }
80 isIPv4 x = all isNibble $ splitOn "." x
82 if all isDigit x then (read x :: Int) < 256
84 parseJSON (Object o) = NmcDom
100 <*> o .:? "fingerprint"
105 emptyNmcDom = NmcDom Nothing Nothing Nothing Nothing Nothing Nothing
106 Nothing Nothing Nothing Nothing Nothing Nothing
107 Nothing Nothing Nothing Nothing Nothing Nothing
109 -- FIXME -- I hope there exists a better way to merge records!
110 mergeNmcDom :: NmcDom -> NmcDom -> NmcDom
111 mergeNmcDom sub dom = dom { domService = choose domService
112 , domIp = choose domIp
113 , domIp6 = choose domIp6
114 , domTor = choose domTor
115 , domI2p = choose domI2p
116 , domFreenet = choose domFreenet
117 , domAlias = choose domAlias
118 , domTranslate = choose domTranslate
119 , domEmail = choose domEmail
120 , domLoc = choose domLoc
121 , domInfo = choose domInfo
122 , domNs = choose domNs
123 , domDelegate = choose domDelegate
124 , domImport = choose domImport
125 , domMap = choose domMap
126 , domFingerprint = choose domFingerprint
127 , domTls = choose domTls
128 , domDs = choose domDs
131 choose :: (NmcDom -> Maybe a) -> Maybe a
132 choose field = case field dom of
136 -- | Perform query and return error string or parsed domain object
138 (String -> IO (Either String ByteString)) -- ^ query operation action
140 -> IO (Either String NmcDom) -- ^ error string or domain
141 queryNmcDom queryOp key = do
144 Left estr -> return $ Left estr
145 Right str -> case decode str :: Maybe NmcDom of
146 Nothing -> return $ Left $ "Unparseable value: " ++ (show str)
147 Just dom -> return $ Right dom
149 -- | Try to fetch "import" object and merge it into the base domain
150 -- Original "import" element is removed, but new imports from the
151 -- imported objects are processed recursively until there are none.
153 (String -> IO (Either String ByteString)) -- ^ query operation action
154 -> NmcDom -- ^ base domain
155 -> IO (Either String NmcDom) -- ^ result with merged import
156 mergeImport queryOp base = do
158 mbase = mergeSelf base
159 base' = mbase {domImport = Nothing}
161 case domImport mbase of
162 Nothing -> return $ Right base'
164 sub <- queryNmcDom queryOp key
166 Left e -> return $ Left e
167 Right sub' -> mergeImport queryOp $ sub' `mergeNmcDom` base'
169 -- | If there is an element in the map with key "", merge the contents
170 -- and remove this element. Do this recursively.
171 mergeSelf :: NmcDom -> NmcDom
175 base' = base {domMap = removeSelf map}
176 removeSelf Nothing = Nothing
177 removeSelf (Just map) = if size map' == 0 then Nothing else Just map'
178 where map' = M.delete "" map
183 case M.lookup "" map' of
185 Just sub -> (mergeSelf sub) `mergeNmcDom` base'
187 -- | Presence of some elements require removal of some others
188 normalizeDom :: NmcDom -> NmcDom
189 normalizeDom dom = foldr id dom [ nsNormalizer
190 , translateNormalizer
193 nsNormalizer dom = case domNs dom of
195 Just ns -> emptyNmcDom { domNs = domNs dom, domEmail = domEmail dom }
196 translateNormalizer dom = case domTranslate dom of
198 Just tr -> dom { domMap = Nothing }
200 -- | Merge imports and Selfs and follow the maps tree to get dom
202 (String -> IO (Either String ByteString)) -- ^ query operation action
203 -> [String] -- ^ subdomain chain
204 -> NmcDom -- ^ base domain
205 -> IO (Either String NmcDom) -- ^ fully processed result
206 descendNmcDom queryOp subdom base = do
207 base' <- mergeImport queryOp base
209 [] -> return $ fmap normalizeDom base'
212 Left err -> return base'
214 case domMap base'' of
215 Nothing -> return $ Right emptyNmcDom
217 case M.lookup d map of
218 Nothing -> return $ Right emptyNmcDom
219 Just sub -> descendNmcDom queryOp ds sub
221 -- | Initial NmcDom populated with "import" only, suitable for "descend"
223 String -- ^ domain key (without namespace prefix)
224 -> NmcDom -- ^ resulting seed domain
225 seedNmcDom dn = emptyNmcDom { domImport = Just ("d/" ++ dn)}