heyefi-0.1.0.1: 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.Types (HEyefiM, HEyefiApplication, lastSNonce)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import Control.Arrow ((>>>))
import Control.Monad.IO.Class (liftIO)
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 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.Core ( runX
, readString
, getText
, (/>))
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 ((show sa) ++ " is not a defined SoapAction yet")
_ -> 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))]
handleSoapAction :: SoapAction -> BL.ByteString -> HEyefiApplication
handleSoapAction StartSession body _ f = do
logInfo "Got StartSession request"
let xmlDocument = readString [] (toString body)
let getTagText = \ s -> liftIO (runX (xmlDocument >>> css s /> getText))
macaddress <- getTagText "macaddress"
cnonce <- getTagText "cnonce"
transfermode <- getTagText "transfermode"
transfermodetimestamp <- getTagText "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
logInfo "Got GetPhotoStatus request"
credentialGood <- checkCredential body
if credentialGood then do
responseBody <- getPhotoStatusResponse
response <- mkResponse responseBody
liftIO (f response)
else do
liftIO (f mkUnauthorizedResponse)
handleSoapAction MarkLastPhotoInRoll _ _ f = do
logInfo "Got MarkLastPhotoInRoll request"
responseBody <- markLastPhotoInRollResponse
response <- mkResponse responseBody
liftIO (f response)
checkCredential :: BL.ByteString -> HEyefiM Bool
checkCredential body = do
let xmlDocument = readString [] (toString body)
let getTagText = \ s -> liftIO (runX (xmlDocument >>> css s /> getText))
macaddress <- getTagText "macaddress"
credential <- getTagText "credential"
state <- get
let snonce = lastSNonce state
upload_key_0 <- getUploadKeyForMacaddress (head macaddress)
case upload_key_0 of
Nothing -> do
logInfo ("No upload key found in configuration for macaddress: " ++ (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 ("Invalid credential in GetPhotoStatus request. Expected: "
++ expectedCredential ++ " Actual: " ++ (head credential))
return False
else
return True