packages feed

heyefi-2.0.0.0: src/HEyefi/Soap.hs

module HEyefi.Soap where

import           Control.Arrow ((>>>))
import           Control.Monad (join)
import           Control.Monad.IO.Class (liftIO)
import           Control.Monad.State.Lazy (get)
import qualified Data.ByteString as B
import           Data.ByteString.Lazy (fromStrict)
import qualified Data.ByteString.Lazy as BL
import           Data.ByteString.Lazy.UTF8 (toString)
import           Data.ByteString.UTF8 (fromString)
import qualified Data.CaseInsensitive as CI
import           Data.List (find)
import           Data.Maybe (fromJust, isJust)
import           Data.Text.Encoding (encodeUtf8)
import           Data.Time.Clock (getCurrentTime, UTCTime)
import           Data.Time.Format (
    formatTime
  , rfc822DateFormat
  , defaultTimeLocale )
import           HEyefi.Config (getUploadKeyForMacaddress)
import           HEyefi.Hex (unhex)
import           HEyefi.Log (logInfo, logDebug)
import           HEyefi.Prelude
import           HEyefi.SoapResponse (
    markLastPhotoInRollResponse
  , getPhotoStatusResponse )
import           HEyefi.StartSession (startSessionResponse)
import           HEyefi.Types
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 (
    getText
  , (/>)
  , runLA
  , xreadDoc )

soapActionHeaderName :: HeaderName
soapActionHeaderName = CI.mk "SoapAction"

headerToSoapAction :: Header -> Maybe SoapAction
headerToSoapAction h |
  h == (soapActionHeaderName, "\"urn:StartSession\"") = Just StartSession
headerToSoapAction h |
  h == (soapActionHeaderName, "\"urn:GetPhotoStatus\"") = Just GetPhotoStatus
headerToSoapAction h |
  h == (soapActionHeaderName, "\"urn:MarkLastPhotoInRoll\"") =
    Just MarkLastPhotoInRoll
headerToSoapAction _ = Nothing

firstJust :: (a -> Maybe b) -> [a] -> Maybe b
firstJust f = join . find isJust . map f

soapAction :: Request -> Maybe SoapAction
soapAction req = firstJust headerToSoapAction (requestHeaders req)

mkResponse :: Text -> HEyefiM Response
mkResponse responseBody = do
  t <- liftIO getCurrentTime
  return (responseLBS
          status200
          (defaultResponseHeaders t (tlength responseBody))
          (fromStrict (encodeUtf8 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))]

firstTag :: BL.ByteString ->
            Text ->
            Text
firstTag body tagName =
  pack (head (runLA (xreadDoc >>> css (unpack tagName) /> getText)
                (toString body)))

handleSoapAction :: SoapAction -> BL.ByteString -> HEyefiApplication
handleSoapAction StartSession body _ f = do
  logDebug gotStartSessionRequest
  let tag = firstTag body
  let macaddress = tag "macaddress"
  let cnonce = tag "cnonce"
  let transfermode = tag "transfermode"
  let transfermodetimestamp = tag "transfermodetimestamp"
  logDebug macaddress
  logDebug transfermodetimestamp
  responseBody <- startSessionResponse
                   macaddress
                   cnonce
                   transfermode
                   transfermodetimestamp
  logDebug responseBody
  response <- mkResponse responseBody
  liftIO (f response)
handleSoapAction GetPhotoStatus body _ f = do
  logDebug gotGetPhotoStatusRequest
  credentialGood <- checkCredential body
  if credentialGood then do
    response <- mkResponse getPhotoStatusResponse
    liftIO (f response)
  else
    liftIO (f mkUnauthorizedResponse)
handleSoapAction MarkLastPhotoInRoll _ _ f = do
  logDebug gotMarkLastPhotoInRollRequest
  response <- mkResponse markLastPhotoInRollResponse
  liftIO (f response)

checkCredential :: BL.ByteString -> HEyefiM Bool
checkCredential body = do
  let tag = firstTag body
  let macaddress = tag "macaddress"
  let credential = tag "credential"
  state <- get
  let snonce = lastSNonce state
  upload_key_0 <- getUploadKeyForMacaddress macaddress
  case upload_key_0 of
   Nothing -> do
     logInfo (noUploadKeyInConfiguration macaddress)
     return False
   Just upload_key_0' -> do
     let credentialString = macaddress <> upload_key_0' <> snonce
     let binaryCredentialString = unhex credentialString
     let expectedCredential = tmd5 (fromJust binaryCredentialString)
     if credential /= expectedCredential then do
       logInfo (invalidCredential expectedCredential credential)
       return False
     else
       return True