heyefi-0.1.1.0: src/HEyefi/Soap.hs
{-# LANGUAGE OverloadedStrings #-}
module HEyefi.Soap
( handleSoapAction
, soapAction
, mkResponse )
where
import HEyefi.Config (getUploadKeyForMacaddress)
import HEyefi.GetPhotoStatus (getPhotoStatusResponse)
import HEyefi.Hex (unhex)
import HEyefi.Log (logInfo, logDebug)
import HEyefi.MarkLastPhotoInRoll (markLastPhotoInRollResponse)
import HEyefi.StartSession (startSessionResponse)
import HEyefi.Strings
import HEyefi.Types (HEyefiM, HEyefiApplication, lastSNonce)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import Control.Arrow ((>>>))
import Control.Arrow.IOStateListArrow (IOSLA)
import Control.Monad.IO.Class (liftIO, MonadIO)
import Control.Monad.State.Lazy (get)
import Data.ByteString.Lazy (fromStrict)
import Data.ByteString.Lazy.UTF8 (toString)
import Data.ByteString.UTF8 (fromString)
import qualified Data.CaseInsensitive as CI
import Data.Hash.MD5 (md5s, Str (..))
import Data.List (find)
import Data.Maybe (fromJust)
import Data.Time.Clock (getCurrentTime, UTCTime)
import Data.Time.Format (formatTime, rfc822DateFormat, defaultTimeLocale)
import Data.Tree.NTree.TypeDefs (NTree)
import Network.HTTP.Types (status200, unauthorized401)
import Network.HTTP.Types.Header (
hContentType,
hServer,
hContentLength,
hDate,
Header,
HeaderName )
import Network.Wai (
responseLBS
, Request
, Response
, requestHeaders )
import Text.HandsomeSoup (css)
import Text.XML.HXT.Arrow.XmlState.TypeDefs (XIOState)
import Text.XML.HXT.Core (
runX
, readString
, getText
, (/>) )
import Text.XML.HXT.DOM.TypeDefs (XNode, XmlTree)
data SoapAction = StartSession
| GetPhotoStatus
| MarkLastPhotoInRoll
deriving (Show, Eq)
headerIsSoapAction :: Header -> Bool
headerIsSoapAction ("SOAPAction",_) = True
headerIsSoapAction _ = False
soapAction :: Request -> Maybe SoapAction
soapAction req =
case find headerIsSoapAction (requestHeaders req) of
Just (_,"\"urn:StartSession\"") -> Just StartSession
Just (_,"\"urn:GetPhotoStatus\"") -> Just GetPhotoStatus
Just (_,"\"urn:MarkLastPhotoInRoll\"") -> Just MarkLastPhotoInRoll
Just (_,sa) -> error (notADefinedSoapAction (show sa))
_ -> Nothing
mkResponse :: String -> HEyefiM Response
mkResponse responseBody = do
t <- liftIO getCurrentTime
return (responseLBS
status200
(defaultResponseHeaders t (length responseBody))
(fromStrict (fromString responseBody)))
mkUnauthorizedResponse :: Response
mkUnauthorizedResponse = responseLBS unauthorized401 [] ""
defaultResponseHeaders :: UTCTime ->
Int ->
[(HeaderName, B.ByteString)]
defaultResponseHeaders time size =
[ (hContentType, "text/xml; charset=\"utf-8\"")
, (hDate, fromString (formatTime defaultTimeLocale rfc822DateFormat time))
, (CI.mk "Pragma", "no-cache")
, (hServer, "Eye-Fi Agent/2.0.4.0 (Windows XP SP2)")
, (hContentLength, fromString (show size))]
getTagText :: Control.Monad.IO.Class.MonadIO m =>
Control.Arrow.IOStateListArrow.IOSLA
(Text.XML.HXT.Arrow.XmlState.TypeDefs.XIOState ())
Text.XML.HXT.DOM.TypeDefs.XmlTree
(Data.Tree.NTree.TypeDefs.NTree Text.XML.HXT.DOM.TypeDefs.XNode)
-> String
-> m [String]
getTagText xmlDocument s = liftIO (runX (xmlDocument >>> css s /> getText))
handleSoapAction :: SoapAction -> BL.ByteString -> HEyefiApplication
handleSoapAction StartSession body _ f = do
logDebug gotStartSessionRequest
let xmlDocument = readString [] (toString body)
macaddress <- getTagText xmlDocument "macaddress"
cnonce <- getTagText xmlDocument "cnonce"
transfermode <- getTagText xmlDocument "transfermode"
transfermodetimestamp <- getTagText xmlDocument "transfermodetimestamp"
logDebug (show macaddress)
logDebug (show transfermodetimestamp)
responseBody <- startSessionResponse
(head macaddress)
(head cnonce)
(head transfermode)
(head transfermodetimestamp)
logDebug (show responseBody)
response <- mkResponse responseBody
liftIO (f response)
handleSoapAction GetPhotoStatus body _ f = do
logDebug gotGetPhotoStatusRequest
credentialGood <- checkCredential body
if credentialGood then do
responseBody <- getPhotoStatusResponse
response <- mkResponse responseBody
liftIO (f response)
else
liftIO (f mkUnauthorizedResponse)
handleSoapAction MarkLastPhotoInRoll _ _ f = do
logDebug gotMarkLastPhotoInRollRequest
responseBody <- markLastPhotoInRollResponse
response <- mkResponse responseBody
liftIO (f response)
checkCredential :: BL.ByteString -> HEyefiM Bool
checkCredential body = do
let xmlDocument = readString [] (toString body)
macaddress <- getTagText xmlDocument "macaddress"
credential <- getTagText xmlDocument "credential"
state <- get
let snonce = lastSNonce state
upload_key_0 <- getUploadKeyForMacaddress (head macaddress)
case upload_key_0 of
Nothing -> do
logInfo (noUploadKeyInConfiguration (head macaddress))
return False
Just upload_key_0' -> do
let credentialString = head macaddress ++ upload_key_0' ++ snonce
let binaryCredentialString = unhex credentialString
let expectedCredential = md5s (Str (fromJust binaryCredentialString))
if head credential /= expectedCredential then do
logInfo (invalidCredential expectedCredential (head credential))
return False
else
return True