1 {-# LANGUAGE OverloadedStrings #-}
3 module NmcDom ( NmcDom(..)
11 import Prelude hiding (length)
12 import Control.Applicative ((<$>), (<*>), empty, pure)
14 import Data.Text (Text, unpack)
15 import Data.List (union)
16 import Data.List.Split
17 import Data.Vector ((!), length, singleton)
18 import Data.Map (Map, unionWith)
19 import qualified Data.HashMap.Strict as H (lookup)
21 import Data.Aeson.Types
22 import Data.Default.Class
24 -- Variant of Aeson's `.:?` that interprets a String as a
25 -- single-element list, so it is possible to have either
29 -- with the same result.
30 (.:/) :: (FromJSON a) => Object -> Text -> Parser (Maybe a)
31 obj .:/ key = case H.lookup key obj of
32 Nothing -> pure Nothing
34 String s -> parseJSON $ Array (singleton v)
37 class Mergeable a where
38 merge :: a -> a -> a -- bias towads second arg
40 instance (Ord k, Mergeable a) => Mergeable (Map k a) where
41 merge mx my = unionWith merge my mx
43 -- Alas, the following is not possible in Haskell :-(
44 -- instance Mergeable String where
47 instance Mergeable Value where
50 instance Mergeable a => Mergeable (Maybe a) where
51 merge (Just x) (Just y) = Just (merge x y)
52 merge Nothing (Just y) = Just y
53 merge (Just x) Nothing = Just x
54 merge Nothing Nothing = Nothing
56 instance Eq a => Mergeable [a] where
57 merge xs ys = union xs ys
59 data NmcRRService = NmcRRService
68 instance FromJSON NmcRRService where
70 if length a == 6 then NmcRRService
80 instance Mergeable NmcRRService where
83 data NmcRRI2p = NmcRRI2p
84 { i2pDestination :: Maybe String
85 , i2pName :: Maybe String
86 , i2pB32 :: Maybe String
89 instance FromJSON NmcRRI2p where
90 parseJSON (Object o) = NmcRRI2p
91 <$> o .:? "destination"
96 instance Mergeable NmcRRI2p where
99 data NmcRRTls = NmcRRTls
100 { tlsMatchType :: Int -- 0:exact 1:sha256 2:sha512
101 , tlsMatchValue :: String
102 , tlsIncSubdoms :: Int -- 1:enforce on subdoms 0:no
103 } deriving (Show, Eq)
105 instance FromJSON NmcRRTls where
106 parseJSON (Array a) =
107 if length a == 3 then NmcRRTls
108 <$> parseJSON (a ! 0)
109 <*> parseJSON (a ! 1)
110 <*> parseJSON (a ! 2)
114 instance Mergeable NmcRRTls where
117 data NmcRRDs = NmcRRDs
121 , dsHashValue :: String
122 } deriving (Show, Eq)
124 instance FromJSON NmcRRDs where
125 parseJSON (Array a) =
126 if length a == 4 then NmcRRDs
127 <$> parseJSON (a ! 0)
128 <*> parseJSON (a ! 1)
129 <*> parseJSON (a ! 2)
130 <*> parseJSON (a ! 3)
134 instance Mergeable NmcRRDs where
137 data NmcDom = NmcDom { domService :: Maybe [NmcRRService]
138 , domIp :: Maybe [String]
139 , domIp6 :: Maybe [String]
140 , domTor :: Maybe String
141 , domI2p :: Maybe NmcRRI2p
142 , domFreenet :: Maybe String
143 , domAlias :: Maybe String
144 , domTranslate :: Maybe String
145 , domEmail :: Maybe String
146 , domLoc :: Maybe String
147 , domInfo :: Maybe Value
148 , domNs :: Maybe [String]
149 , domDelegate :: Maybe String
150 , domImport :: Maybe [String]
151 , domMap :: Maybe (Map String NmcDom)
152 , domFingerprint :: Maybe [String]
153 , domTls :: Maybe (Map String
154 (Map String [NmcRRTls]))
155 , domDs :: Maybe [NmcRRDs]
156 , domMx :: Maybe [String] -- Synthetic
157 , domSrv :: Maybe [String] -- Synthetic
158 } deriving (Show, Eq)
160 instance Default NmcDom where
161 def = NmcDom Nothing Nothing Nothing Nothing Nothing Nothing Nothing
162 Nothing Nothing Nothing Nothing Nothing Nothing Nothing
163 Nothing Nothing Nothing Nothing Nothing Nothing
165 instance FromJSON NmcDom where
166 -- Wherever we expect a domain object, there may be a string
167 -- containing IPv4 address. Interpret it as such.
168 -- Question: shall we try to recognize IPv6 addresses too?
169 parseJSON (String s) =
170 return $ if isIPv4 s'
171 then def { domIp = Just [s'] }
175 isIPv4 x = all isNibble $ splitOn "." x
177 if all isDigit x then (read x :: Int) < 256
179 parseJSON (Object o) = NmcDom
187 <*> o .:? "translate"
195 <*> o .:/ "fingerprint"
198 <*> return Nothing -- domMx not parsed
199 <*> return Nothing -- domSrv not parsed
202 instance Mergeable NmcDom where
203 merge sub dom = dom { domService = mergelm domService
204 , domIp = mergelm domIp
205 , domIp6 = mergelm domIp6
206 , domTor = choose domTor
207 , domI2p = mergelm domI2p
208 , domFreenet = choose domFreenet
209 , domAlias = choose domAlias
210 , domTranslate = choose domTranslate
211 , domEmail = choose domEmail
212 , domLoc = choose domLoc
213 , domInfo = mergelm domInfo
214 , domNs = mergelm domNs
215 , domDelegate = mergelm domDelegate
216 , domImport = mergelm domImport
217 , domMap = mergelm domMap
218 , domFingerprint = mergelm domFingerprint
219 , domTls = mergelm domTls
220 , domDs = mergelm domDs
221 , domMx = mergelm domMx
222 , domSrv = mergelm domSrv
225 mergelm x = merge (x sub) (x dom)
226 -- Because it is not possible to define instance of merge for Strings,
227 -- we have to treat string elements separately, otherwise strings are
228 -- 'unioned' along with the rest of lists. Ugly, but alternatives are worse.
229 choose field = case field dom of
233 mergeNmcDom :: NmcDom -> NmcDom -> NmcDom