@@ -44,7 +44,7 @@ import Data.Maybe (listToMaybe, fromMaybe)
44import Data.Word (Word8)
44import Data.Word (Word8)
45import Data.Int (Int32, Int64)
45import Data.Int (Int32, Int64)
46import Data.Typeable (Typeable)
46import Data.Typeable (Typeable)
47−import Control.Monad (guard, replicateM, replicateM_)
47+import Control.Monad (guard, replicateM, replicateM_, when)
48import Control.Exception.Extensible (try, throw, Exception, ErrorCall)
48import Control.Exception.Extensible (try, throw, Exception, ErrorCall)
50-- | An exception indicating that the binary data being parsed was not
50-- | An exception indicating that the binary data being parsed was not
@@ -91,26 +91,23 @@ formatError fp e = throwOlson fp $ show e
91-- | A binary parser for binary Olson timezone files
91-- | A binary parser for binary Olson timezone files
92getOlson :: Get OlsonData
92getOlson :: Get OlsonData
93getOlson = do
93getOlson = do
94− getByteString 4
94+ (version, part1) <- getOlsonPart True get32bitInteger
95− >>= verify ((== "TZif") . toASCII . B.unpack) "missing magic number"
97− >>= verify (`elem` [0, 50]) "invalid version number"
98− replicateM_ 15 getWord8 -- padding nulls
99− getOlsonParts version
101−-- There is one part for Version 1 format data, and two parts and a POSIX
95+ -- There is one part for Version 1 format data, and two parts and a POSIX
102−-- TZ string for Version 2 format data
96+ -- TZ string for Version 2 format data
103−getOlsonParts :: Word8 -> Get OlsonData
97+ case version of
104−getOlsonParts 0 = getOlsonPart get32bitInteger
98+ 0 -> return part1
105−getOlsonParts 50 = do part1 <- getOlsonPart get32bitInteger
106− part2 <- getOlsonPart get64bitInteger
99+ 50 -> do (_, part2) <- getOlsonPart False get64bitInteger
107− posixTZ <- getPosixTZ
100+ posixTZ <- getPosixTZ
108− return $ part1 `mappend` part2 `mappend` posixTZ
101+ return $ part1 `mappend` part2 `mappend` posixTZ
109−getOlsonParts _ = error "Invalid version number" -- never reached
102+ _ -> verify (const False) "invalid version number" undefined
111-- Parse the part of an Olson file that contains timezone data
104-- Parse the part of an Olson file that contains timezone data
112−getOlsonPart :: Integral a => Get a -> Get OlsonData
105+getOlsonPart :: Integral a => Bool -> Get a -> Get (Word8, OlsonData)
113−getOlsonPart getTime = do
106+getOlsonPart verifyMagic getTime = do
107+ magic <- fmap (toASCII . B.unpack) $ getByteString 4
108+ when verifyMagic $ verify_ (== "TZif") "missing magic number" magic
110+ replicateM_ 15 getWord8 -- padding nulls
114 tzh_ttisgmtcnt <- get32bitInt
111 tzh_ttisgmtcnt <- get32bitInt
115 tzh_ttisstdcnt <- get32bitInt
112 tzh_ttisstdcnt <- get32bitInt
116 tzh_leapcnt <- get32bitInt
113 tzh_leapcnt <- get32bitInt
@@ -124,13 +121,16 @@ getOlsonPart getTime = do
124 leaps <- replicateM tzh_leapcnt $ getLeapInfo getTime
121 leaps <- replicateM tzh_leapcnt $ getLeapInfo getTime
125 isstds <- replicateM tzh_ttisstdcnt getBool
122 isstds <- replicateM tzh_ttisstdcnt getBool
126 isgmts <- replicateM tzh_ttisgmtcnt getBool
123 isgmts <- replicateM tzh_ttisgmtcnt getBool
127− return $ OlsonData
126+ OlsonData
128− (zipWith Transition times indexes)
127+ (zipWith Transition times indexes)
129− (map (flip lookupAbbr abbr_chars) . zipWith setTtype ttinfos .
128+ (map (flip lookupAbbr abbr_chars) . zipWith setTtype ttinfos .
130− (++ repeat UnknownType) $
129+ (++ repeat UnknownType) $
131− zipWith boolsToTType (map Just isstds ++ repeat Nothing) isgmts)
130+ zipWith boolsToTType (map Just isstds ++ repeat Nothing) isgmts)
135 lookupAbbr (TtInfo gmtoff isdst ttype abbrind) =
135 lookupAbbr (TtInfo gmtoff isdst ttype abbrind) =
136 TtInfo gmtoff isdst ttype . takeWhile (/= '\NUL') . drop abbrind
136 TtInfo gmtoff isdst ttype . takeWhile (/= '\NUL') . drop abbrind
@@ -203,6 +203,11 @@ verify pred msg val
203 | pred val = return val
203 | pred val = return val
204 | otherwise = error msg
204 | otherwise = error msg
206+verify_ :: Monad m => (a -> Bool) -> String -> a -> m ()
208+ | pred val = return ()
209+ | otherwise = error msg
206throwOlson :: FilePath -> String -> IO a
211throwOlson :: FilePath -> String -> IO a
207throwOlson fp msg = throw . OlsonError $
212throwOlson fp msg = throw . OlsonError $
208 fp ++ ": invalid timezone file: " ++ msg
213 fp ++ ": invalid timezone file: " ++ msg