153 lines
5.9 KiB
Plaintext
153 lines
5.9 KiB
Plaintext
module Main where
|
|
|
|
import Prelude
|
|
import AviaryFormat.Format (Format, Image, Index, parseIndex) as Format
|
|
|
|
import Control.Monad.Error.Class (try)
|
|
import Effect (Effect)
|
|
import Effect.Aff (Aff, launchAff)
|
|
import Effect.Class (liftEffect)
|
|
import Effect.Console (log)
|
|
import Effect.Exception (message)
|
|
import Fetch (fetch)
|
|
import Data.Array (head)
|
|
import Data.Base64 (decodeBase64, fromString)
|
|
import Data.Either (Either(..), hush, note)
|
|
import Data.Maybe (Maybe(..))
|
|
import Data.Newtype (unwrap)
|
|
import Data.ArrayBuffer.ArrayBuffer (byteLength)
|
|
import Data.ArrayBuffer.Builder (execPut, putInt8)
|
|
import Data.ArrayBuffer.Builder.Internal (toView)
|
|
import Data.ArrayBuffer.DataView (buffer, whole)
|
|
import Data.ArrayBuffer.Types (ArrayBuffer)
|
|
import Data.Filterable (partitionMap)
|
|
import Data.Foldable (traverse_)
|
|
import Data.String (drop, indexOf', indexOf, splitAt)
|
|
import Data.String.Pattern (Pattern(..))
|
|
import Parsing (runParserT)
|
|
import Web.HTML (window) as HTML
|
|
import Web.HTML.Window (location)
|
|
import Web.HTML.Location (hash, origin, pathname) as Location
|
|
|
|
import Crypto.Subtle.Constants.AES (aesGCM, t128)
|
|
import Crypto.Subtle.Encrypt (aesGCM, decrypt) as Alg
|
|
import Crypto.Subtle.Key.Import (aes)
|
|
import Crypto.Subtle.Key.Import (importKey) as SC
|
|
import Crypto.Subtle.Key.Types (CryptoKey, decrypt, raw)
|
|
|
|
nonceE :: Effect ArrayBuffer
|
|
nonceE = execPut $ traverse_ putInt8 [0xd0, 0xc3, 0x75, 0x56, 0x58, 0xc1, 0x7e, 0x5f, 0xd6, 0xcc, 0xb6, 0x76]
|
|
|
|
data AviaryError
|
|
= MalformedKey
|
|
| MalformedUrl
|
|
| DecryptFailed
|
|
| StrangeIndex
|
|
| IndexMissingField String
|
|
|
|
type Image =
|
|
{ key :: Aff CryptoKey
|
|
, fullUrl :: String
|
|
, thumbUrl :: String
|
|
, blurhash :: String
|
|
, format :: Format.Format
|
|
}
|
|
|
|
type Index =
|
|
{ images :: Array Image
|
|
, title :: Maybe String
|
|
, desc :: Maybe String
|
|
}
|
|
|
|
instance showAviaryError :: Show AviaryError where
|
|
show MalformedKey = "Malformed key!!"
|
|
show MalformedUrl = "Malformed url!!"
|
|
show DecryptFailed = "Either the data provided by the server was bad, or valid but incorrect key. Expiration likely."
|
|
show StrangeIndex = "The gallery index matched with the provided key, but was in a strange format. This could indicate a severe version mismatch, or that the gallery was created by a malfunctioning client."
|
|
show (IndexMissingField f) = "The gallery index was missing the crucial field " <> f <> ". This is almost certainly the result of a version mismatch or that the creator's client was not fully up-to-spec."
|
|
|
|
type UrlInfo =
|
|
{ server :: String
|
|
, fileId :: String
|
|
, key :: String
|
|
}
|
|
|
|
parseUrl :: String -> Either AviaryError UrlInfo
|
|
parseUrl url = note MalformedUrl do
|
|
slashIndex <- indexOf' (Pattern "/") 8 url
|
|
let { after, before: server } = splitAt (1 + slashIndex) url
|
|
poundIndex <- indexOf (Pattern "#") after
|
|
let { after: key, before: fileId } = splitAt poundIndex after
|
|
Just { server, fileId, key: drop 1 key }
|
|
|
|
decodeKey :: String -> Either AviaryError ArrayBuffer
|
|
decodeKey key = note MalformedKey $ decodeBase64 <$> fromString key
|
|
|
|
importKey :: ArrayBuffer -> Aff CryptoKey
|
|
importKey key =
|
|
SC.importKey raw key (aes aesGCM) false [decrypt]
|
|
|
|
decryptBlob :: CryptoKey -> ArrayBuffer -> ArrayBuffer -> Aff ArrayBuffer
|
|
decryptBlob key cyphertext nonce =
|
|
-- nonce <- makeAff (\_ -> nonceE)
|
|
Alg.decrypt (Alg.aesGCM nonce Nothing (Just t128)) key cyphertext
|
|
|
|
convertImageFromProtobuf :: Format.Image -> Either AviaryError Image
|
|
convertImageFromProtobuf protoimage = let protoimage' = unwrap protoimage in do
|
|
rawKey <- note (IndexMissingField "images[].key") protoimage'.key
|
|
fullUrl <- note (IndexMissingField "images[].full_url") protoimage'.full_url
|
|
thumbUrl <- note (IndexMissingField "images[].thumb_url") protoimage'.thumb_url
|
|
blurhash <- note (IndexMissingField "images[].blurhash") protoimage'.blurhash
|
|
format <- note (IndexMissingField "images[].format") protoimage'.format
|
|
let key = SC.importKey raw (buffer $ toView $ unwrap rawKey) (aes aesGCM) false [decrypt]
|
|
pure { key, fullUrl, thumbUrl, blurhash, format }
|
|
|
|
convertIndexFromProtobuf :: Format.Index -> Either AviaryError Index
|
|
convertIndexFromProtobuf protoindex =
|
|
let protoindex' = unwrap protoindex in
|
|
let {right: images, left: imagesErrors} = partitionMap convertImageFromProtobuf protoindex'.images
|
|
in
|
|
case head imagesErrors of
|
|
Nothing ->
|
|
Right
|
|
{ images
|
|
, title: protoindex'.title
|
|
, desc: protoindex'.desc
|
|
}
|
|
Just err -> Left err
|
|
|
|
parseIndex :: ArrayBuffer -> Effect (Either AviaryError Index)
|
|
parseIndex rawData = do
|
|
parseResult <- runParserT (whole rawData) (Format.parseIndex (byteLength rawData))
|
|
pure $ convertIndexFromProtobuf =<< (note StrangeIndex $ hush parseResult)
|
|
|
|
main_aff :: Aff Unit
|
|
main_aff = do
|
|
urlInfo <- liftEffect do
|
|
window <- HTML.window
|
|
location <- location window
|
|
keyB64 <- drop 1 <$> Location.hash location
|
|
server <- Location.origin location
|
|
fileId <- drop 1 <$> Location.pathname location
|
|
nonce <- nonceE
|
|
pure {keyB64, server, fileId, nonce}
|
|
maybeIndex <- case decodeKey urlInfo.keyB64 of
|
|
Left err -> pure $ Left err
|
|
Right keyBuffer -> do
|
|
cryptoKey <- importKey keyBuffer
|
|
{ status, arrayBuffer } <- fetch (urlInfo.server <> "/" <> urlInfo.fileId <> ".bin") {}
|
|
encryptedIndex <- arrayBuffer
|
|
serializedIndex <- try $ decryptBlob cryptoKey encryptedIndex urlInfo.nonce
|
|
case serializedIndex of
|
|
Left err -> do
|
|
_ <- liftEffect $ log $ message err
|
|
pure $ Left $ DecryptFailed
|
|
Right serializedIndex' -> do
|
|
_ <- liftEffect $ log "BBBBBBBBB!"
|
|
maybeIndex <- liftEffect $ parseIndex serializedIndex'
|
|
pure ((\i -> i.title) <$> maybeIndex)
|
|
liftEffect $ log $ show maybeIndex
|
|
|
|
main :: Effect Unit
|
|
main = launchAff main_aff $> unit
|