packages feed

dropbox-sdk (empty) → 0.1.0

raw patch · 5 files changed

+1253/−0 lines, 5 filesdep +HTTPdep +basedep +blaze-buildersetup-changed

Dependencies added: HTTP, base, blaze-builder, bytestring, certificate, enumerator, http-enumerator, http-types, json, network, old-locale, time, tls, tls-extra, urlencoded, utf8-string

Files

+ License.txt view
@@ -0,0 +1,20 @@+Copyright (c) 2011 Dropbox Inc., http://www.dropbox.com/++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be+included in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Source/Dropbox.hs view
@@ -0,0 +1,851 @@+module Dropbox (+    -- * Configuration+    mkConfig,+    Config(..),+    CertVerifier(..),+    certVerifierInsecure,+    certVerifierFromPemFile,+    certVerifierFromRootCerts,+    AppId(..),+    Hosts(..),+    hostsDefault,+    Locale,+    localeEn, localeEs, localeFr, localeDe, localeJp,+    AccessType(..),+    -- * Manager+    Manager,+    withManager,+    -- * OAuth+    RequestToken(..),+    authStart,+    AccessToken(..),+    authFinish,+    Session(..),+    -- * Get user account info+    getAccountInfo, AccountInfo(..),+    -- * Basic file access API+    -- ** Get metadata+    getMetadata, getMetadataWithChildren, getMetadataWithChildrenIfChanged,+    Meta(..), MetaBase(..), MetaExtra(..), FolderContents(..), FileExtra(..),+    FolderHash(..), FileRevision(..),+    -- ** Uploading files+    addFile, forceFile, updateFile,+    -- * Common data types+    fileRevisionToString, folderHashToString,+    ErrorMessage, URL, Path,+    RequestBody, bsRequestBody+) where++{-+TODO:+- The JSON we get from the server sometimes has numbers encoded as strings+  Make sure we handle that case.+- Proper return values for 404, 406, oauth unlinked, etc.+-}++import Network.HTTP.Base (urlEncode)+import qualified Data.ByteString.UTF8 as UTF8 (toString, fromString)+import qualified Data.URLEncoded as URLEncoded+import qualified Network.URI as URI+import Data.URLEncoded (URLEncoded)+import qualified Text.JSON as JSON+import Text.JSON (JSON, readJSON, showJSON)+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as LBS+import qualified Data.ByteString.Char8 as BS8+import Data.Word (Word64)+import Data.Int (Int64)+import Data.List (isPrefixOf)+import Data.Time.Clock (UTCTime(utctDay), getCurrentTime)+import Data.Time.Format (parseTime, formatTime)+import System.Locale (defaultTimeLocale)+import Control.Monad (liftM)+import qualified Data.Enumerator as E+import qualified Data.Enumerator.List as EL+import qualified Network.HTTP.Enumerator as HE+import qualified Network.HTTP.Types as HT+import qualified Network.TLS as TLS+import qualified Network.TLS.Extra as TLSExtra+import Data.Certificate.X509 (X509)+import qualified Data.Certificate.X509 as X509+import Data.Certificate.PEM as PEM+import Data.Enumerator (Iteratee, Enumerator)+import qualified Blaze.ByteString.Builder.ByteString as BlazeBS+import System.IO as IO+import qualified Paths_dropbox_sdk as Paths++type ErrorMessage = String+type URL = String++-- |Dropbox file and folder paths.  Should always start with "/".+type Path = String++apiVersion = "1"++-- |The type of folder access your Dropbox application uses (<https://www.dropbox.com/developers/start/core>).+data AccessType+    = AccessTypeDropbox   -- ^Full access to the user's entire Dropbox+    | AccessTypeAppFolder -- ^Access to an application-specific "app folder" within the user's Dropbox+    deriving (Show, Eq)++-- |Your application's Dropbox "app key" and "app secret".+data AppId = AppId String String deriving (Show, Eq)++-- |An OAuth request token (returned by 'authStart')+data RequestToken = RequestToken String String deriving (Show, Eq)++-- |An OAuth request token (returned by 'authFinish', used to construct a 'Session')+data AccessToken = AccessToken String String deriving (Show, Eq)++accessTypePath :: AccessType -> String+accessTypePath AccessTypeDropbox = "dropbox"+accessTypePath AccessTypeAppFolder = "sandbox"++accessTypeRoot :: AccessType -> String+accessTypeRoot AccessTypeDropbox = "dropbox"+accessTypeRoot AccessTypeAppFolder = "app_folder"++-- |The set of hosts that serve the Dropbox API.  Just use 'hostsDefault'.+data Hosts = Hosts+    { hostsWeb :: String         -- ^The Dropbox API web host (for OAuth step 2)+    , hostsApi :: String         -- ^The Dropbox API endpoint for most non-content-transferring calls.+    , hostsApiContent :: String  -- ^The Dropbox API endpoint for most content-transferring calls.+    } deriving (Show, Eq)++-- |The standard set of hosts that serve the Dropbox API.  Used to create a 'Config'.+hostsDefault :: Hosts+hostsDefault = Hosts+    { hostsWeb = "www.dropbox.com"+    , hostsApi = "api.dropbox.com"+    , hostsApiContent = "api-content.dropbox.com"+    }++-- |Specifies a locale (the string is a two-letter locale code)+newtype Locale = Locale String deriving (Show, Eq)++-- |The English (American) locale ("en").+localeEn :: Locale+localeEn = Locale "en"++-- |The Spanish locale (@'Locale' "es"@).+localeEs :: Locale+localeEs = Locale "es"++-- |The French locale (Locale "fr").+localeFr :: Locale+localeFr = Locale "fr"++-- |The German locale (Locale "de").+localeDe :: Locale+localeDe = Locale "de"++-- |The Japanese locale (Locale "jp").+localeJp :: Locale+localeJp = Locale "jp"++-- |The configuration used to make authentication calls and API calls.  You typically create+-- one of these via the 'config' helper function.+data Config = Config+    { configHosts :: Hosts           -- ^The hosts to connect to (just use 'hostsDefault').+    , configUserLocale :: Locale     -- ^The locale that the Dropbox service should use when returning user-visible strings.+    , configAppId :: AppId           -- ^Your app's key/secret+    , configAccessType :: AccessType -- ^The type of folder access your Dropbox application uses.+    , configCertVerifier :: CertVerifier -- ^The server certificate validation routine.+    } deriving (Show)++type CertVerifierFunc =+    HT.Ascii                       -- ^The server's host name.+    -> [X509]                      -- ^The server's certificate chain.+    -> IO TLS.TLSCertificateUsage  -- ^Whether the certificate chain is valid or not.++-- |How the server's SSL certificate will be verified.+data CertVerifier = CertVerifier+    { certVerifierName :: String -- ^The human-friendly name of the policy (only for debug prints)+    , certVerifierFunc :: CertVerifierFunc -- ^The function that implements certificate validation.+    }++instance Show CertVerifier where+    show (CertVerifier name _) = "CertVerifier " ++ show name++-- |A convenience function that constructs a 'Config'+mkConfig ::+    Locale+    -> String      -- ^Your Dropbox app key+    -> String      -- ^Your Dropbox app secret+    -> AccessType  -- ^'configAccessType'+    -> IO Config+mkConfig userLocale appKey appSecret accessType = do+    caFile <- Paths.getDataFileName "trusted-certs.crt"+    vf <- do+        r <- certVerifierFromPemFile caFile+        case r of+            Right vf -> return $ vf+            Left err -> fail $ "Unable to load root certificates from " ++ (show caFile) ++ ": " ++ err+    return $ Config+        { configHosts = hostsDefault+        , configUserLocale = userLocale+        , configAppId = AppId appKey appSecret+        , configAccessType = accessType+        , configCertVerifier = vf+        }++-- |Contains a 'Config' and an 'AccessToken'.  Every API call (after OAuth is complete)+-- requires this as an argument.+data Session = Session+    { sessionConfig :: Config+    , sessionAccessToken :: AccessToken  -- ^The 'AccessToken' obtained from 'authFinish'+    }++----------------------------------------------------------------------+-- SSL Certificate Validation++-- |A dummy implementation that doesn't perform any verification.+certVerifierInsecure :: CertVerifier+certVerifierInsecure = CertVerifier "insecure" (\_ _ -> return TLS.CertificateUsageAccept)++rightsOrFirstLeft :: [Either a b] -> Either a [b]+rightsOrFirstLeft = foldr f (Right [])+    where+        f (Left e) _ = Left e+        f _ (Left e) = Left e+        f (Right v) (Right vs) = Right (v:vs)++-- |Reads certificates in PEM format from the given file and uses those as the roots when+-- verifying certificates.  This function basically just loads the certificates and delegates+-- to 'certVerifierFromRootCerts' for the actual checking.+certVerifierFromPemFile :: FilePath -> IO (Either ErrorMessage CertVerifier)+certVerifierFromPemFile filePath = do+    raw <- withFile filePath IO.ReadMode BS.hGetContents+    let pems = PEM.parsePEMs raw+    let es = [X509.decodeCertificate (LBS.fromChunks [stuff]) | (_, stuff) <- pems]+    case rightsOrFirstLeft es of+        Left err -> return $ Left err+        Right x509s -> return $ Right $ CertVerifier ("PEM file: " ++ show filePath) (certVerifierFromRootCerts x509s)++certAll :: [IO TLS.TLSCertificateUsage] -> IO TLS.TLSCertificateUsage+certAll [] = return TLS.CertificateUsageAccept+certAll (head:rest) = do+    r <- head+    case r of+        TLS.CertificateUsageAccept -> certAll rest+        reject -> return $ reject++-- |Given a set of root certificates, yields a certificate validation function.+certVerifierFromRootCerts ::+    [X509]            -- ^The set of trusted root certificates.+    -> HT.Ascii       -- ^The remove server's domain name.+    -> [X509]         -- ^The certificate chain provided by the remote server.+    -> IO TLS.TLSCertificateUsage+-- TODO: Rewrite this crappy code.  SSL cert checking needs to be more correct than this.+certVerifierFromRootCerts roots domain chain = do+        utcTime <- getCurrentTime+        let day = utctDay utcTime+        certAll+            [ return $ TLSExtra.certificateVerifyDomain (BS8.unpack domain) chain+            , checkTrustChain day chain+            ]+    where+        checkTrustChain _ [] = return $ TLS.CertificateUsageReject $ TLS.CertificateRejectOther "empty chain"+        checkTrustChain day (head:rest) = do+            if isUnexpired day head+                then do+                    issuerMatch <- mapM (head `isIssuedBy`) roots+                    if any (== True) issuerMatch+                        then return $ TLS.CertificateUsageAccept+                        else case rest of+                            [] -> return $ TLS.CertificateUsageReject TLS.CertificateRejectUnknownCA+                            (next:_) -> do+                                nextOk <- TLSExtra.certificateVerifyAgainst head next+                                if nextOk+                                    then checkTrustChain day rest+                                    else return $ TLS.CertificateUsageReject $ TLS.CertificateRejectOther "break in verification chain"+                else return $ TLS.CertificateUsageReject $ TLS.CertificateRejectExpired+        isIssuedBy :: X509 -> X509 -> IO Bool+        isIssuedBy c issuer =+            if subjectDN issuer == issuerDN c+                then TLSExtra.certificateVerifyAgainst c issuer+                else return False+        subjectDN c = X509.certSubjectDN $ X509.x509Cert c+        issuerDN c = X509.certIssuerDN $ X509.x509Cert c+        isUnexpired day cert =+            let ((beforeDay, _, _), (afterDay, _, _)) = X509.certValidity (X509.x509Cert cert)+            in beforeDay < day && day <= afterDay++----------------------------------------------------------------------+-- Authentication/Authorization++buildOAuthHeaderNoToken (AppId consumerKey consumerSecret) =+    "OAuth oauth_version=\"1.0\", oauth_signature_method=\"PLAINTEXT\""+    ++ ", oauth_consumer_key=\"" ++ urlEncode consumerKey ++ "\""+    ++ ", oauth_signature=\"" ++ sig ++ "\""+    where+        sig = urlEncode consumerSecret ++ "&"++buildOAuthHeader (AppId consumerKey consumerSecret) (signingKey, signingSecret) =+    "OAuth oauth_version=\"1.0\", oauth_signature_method=\"PLAINTEXT\""+    ++ ", oauth_consumer_key=\"" ++ urlEncode consumerKey ++ "\""+    ++ ", oauth_token=\"" ++ urlEncode signingKey ++ "\""+    ++ ", oauth_signature=\"" ++ sig ++ "\""+    where+        sig = urlEncode consumerSecret ++ "&" ++ urlEncode signingSecret++-- |OAuth step 1.  If successful, returns a 'RequestToken' (to be used with+-- 'authFinish' eventually) and an authorization URL that you should redirect the user+-- to next.  If you provide a callback URL (optional), then the authorization URL you+-- send the user to will redirect to your callback URL after the user authorizes your+-- application.+authStart ::+    Manager      -- ^The HTTP connection manager to use.+    -> Config+    -> Maybe URL -- ^The callback URL (optional)+    -> IO (Either ErrorMessage (RequestToken, URL))+authStart mgr config callback = do+    result <- httpClientGet mgr vf uri oauthHeader (mkHandler handler)+    return $ mergeLefts result+    where+        Locale locale = configUserLocale config+        host = hostsApi (configHosts config)+        webHost = hostsWeb (configHosts config)+        consumerPair = configAppId config+        uri = "https://" ++ host ++ ":443/" ++ apiVersion ++ "/oauth/request_token?locale=" ++ urlEncode locale+        oauthHeader = buildOAuthHeaderNoToken consumerPair+        vf = certVerifierFunc $ configCertVerifier config+        handler 200 _ body = do+            let sBody = UTF8.toString body  -- toString should return a Maybe, but it doesn't.  You too, Haskell?+            case parseTokenParts sBody of+                Left err -> Left $ "couldn't understand response from Dropbox: " ++ err+                Right requestToken@(RequestToken requestTokenKey _) -> do+                    let authorizeUrl = "https://" ++ webHost ++ "/"++apiVersion++"/oauth/authorize?locale=" ++ urlEncode locale ++ "&oauth_token=" ++ urlEncode requestTokenKey ++ callbackSuffix+                    Right (requestToken, authorizeUrl)+        handler code reason body = Left $ "server returned " ++ show code ++ ": " ++ show reason ++ ": " ++ show body+        callbackSuffix = case callback of+            Nothing -> ""+            Just callbackUrl -> "&oauth_callback=" ++ urlEncode callbackUrl+        parseTokenParts :: String -> Either String RequestToken+        parseTokenParts s = do+            enc <- URLEncoded.importString s+            key <- requireKey enc "oauth_token"+            secret <- requireKey enc "oauth_token_secret"+            return $ RequestToken key secret++-- |OAuth step 3.  Once you've directed the user to the authorization URL from 'authStart'+-- and the user has authorized your app, call this function to get a 'RequestToken', which+-- is used to make Dropbox API calls.+authFinish ::+    Manager       -- ^The HTTP connection manager to use.+    -> Config+    -> RequestToken  -- ^The 'RequestToken' obtained from 'authStart'+    -> IO (Either ErrorMessage (AccessToken, String))+        -- ^The 'AccessToken' used to make Dropbox API calls and the user's Dropbox user ID.+authFinish mgr config (RequestToken rtKey rtSecret) = do+    result <- httpClientGet mgr vf uri oauthHeader (mkHandler handler)+    return $ mergeLefts result+    where+        host = hostsApi (configHosts config)+        (Locale locale) = configUserLocale config+        consumerPair = configAppId config+        uri = "https://" ++ host ++ ":443/"++apiVersion++"/oauth/access_token?locale=" ++ urlEncode locale+        oauthHeader = buildOAuthHeader consumerPair (rtKey, rtSecret)+        vf = certVerifierFunc $ configCertVerifier config+        handler 200 _ body = do+            let sBody = UTF8.toString body  -- toString should return a Maybe, but it doesn't.  You too, Haskell?+            case parseResponse sBody of+                Left err -> Left $ "couldn't understand response from Dropbox: " ++ err+                Right value -> Right value+        handler code reason body = Left $ "server returned " ++ show code ++ ": " ++ show reason ++ ": " ++ show body+        parseResponse :: String -> Either String (AccessToken, String)+        parseResponse s = do+            enc <- URLEncoded.importString s+            key <- requireKey enc "oauth_token"+            secret <- requireKey enc "oauth_token_secret"+            userId <- requireKey enc "uid"+            return $ (AccessToken key secret, userId)++requireKey :: URLEncoded -> String -> Either String String+requireKey enc name = case URLEncoded.lookup name enc of+    Just value -> return value+    Nothing -> Left $ "missing parameter \"" ++ name ++ "\""++----------------------------------------------------------------------++dbTimeFormat = "%a, %d %b %Y %H:%M:%S %z"++dbFormatTime = formatTime defaultTimeLocale dbTimeFormat+dbParseTime = parseTime defaultTimeLocale dbTimeFormat++-- JSON parse helpers+readJsonFieldT :: JSON a => String -> [(String, JSON.JSValue)] -> b -> (a -> b) -> JSON.Result b+readJsonFieldT a as d t = case lookup a as of+    Just jv -> do+        v <- readJSON jv+        return (t v)+    Nothing -> JSON.Ok d++readJsonFieldD :: JSON a => String -> [(String, JSON.JSValue)] -> a -> JSON.Result a+readJsonFieldD a as d = readJsonFieldT a as d id++readJsonField :: JSON a => String -> [(String, JSON.JSValue)] -> JSON.Result a+readJsonField a as = maybe (fail $ "missing field \"" ++ a ++ "\"") return (lookup a as) >>= readJSON++handleJsonBodyT :: JSON a => (a -> b) -> ByteString -> Either ErrorMessage b+handleJsonBodyT tf body = case JSON.decode $ UTF8.toString body of+    JSON.Ok v -> Right (tf v)+    JSON.Error err -> Left $ "couldn't parse response from Dropbox: " ++ err++handleJsonBody :: JSON a => ByteString -> Either ErrorMessage a+handleJsonBody = handleJsonBodyT id++----------------------------------------------------------------------+-- GetAccountInfo++-- |Information about a user account.+data AccountInfo = AccountInfo+    { accountInfoUid :: Word64            -- ^Dropbox user ID+    , accountInfoDisplayName :: String    -- ^Full name (when displayed as a single string)+    , accountInfoCountry :: Maybe String  -- ^Two-letter country code, if available+    , accountInfoReferralUrl :: String    -- ^Dropbox referral link+    , accountInfoQuota :: Quota           -- ^Information about the storage quota+    } deriving (Show, Eq)++data Quota = Quota+    { quotaTotal :: Word64    -- ^Total space allocation (bytes)+    , quotaNormal :: Word64   -- ^Space used outside of shared folders (bytes)+    , quotaShared :: Word64   -- ^Space used in shared folders (bytes)+    } deriving (Show, Eq)++instance JSON AccountInfo where+    showJSON a = JSON.makeObj+        [ ("uid", showJSON $ accountInfoUid a)+        , ("display_name", showJSON $ accountInfoDisplayName a)+        , ("country", showJSON $ accountInfoCountry a)+        , ("referral_link", showJSON $ accountInfoReferralUrl a)+        , ("quota_info", showJSON $ accountInfoQuota a)+        ]+    readJSON (JSON.JSObject obj) = do+        uid <- readJsonField "uid" m+        displayName <- readJsonField "display_name" m+        country <- readJsonFieldT "country" m Nothing Just+        referralUrl <- readJsonField "referral_link" m+        quota <- readJsonField "quota_info" m+        return $ AccountInfo+            { accountInfoUid = uid+            , accountInfoDisplayName = displayName+            , accountInfoCountry = country+            , accountInfoReferralUrl = referralUrl+            , accountInfoQuota = quota+            }+        where m = JSON.fromJSObject obj+    readJSON _ = fail "expecting an object"++instance JSON Quota where+    showJSON q = JSON.makeObj+        [ ("quota", showJSON $ quotaTotal q)+        , ("normal", showJSON $ quotaNormal q)+        , ("shared", showJSON $ quotaShared q)+        ]+    readJSON (JSON.JSObject obj) = do+        total <- readJsonField "quota" m+        normal <- readJsonField "normal" m+        shared <- readJsonField "shared" m+        return $ Quota+            { quotaTotal = total+            , quotaNormal = normal+            , quotaShared = shared+            }+        where m = JSON.fromJSObject obj+    readJSON _ = fail "expecting an object"++-- |Retrieve information about the user account your 'AccessToken' is connected to.+getAccountInfo ::+    Manager     -- ^The HTTP connection manager to use.+    -> Session+    -> IO (Either ErrorMessage AccountInfo)+getAccountInfo mgr session = do+    result <- doGet mgr session hostsApi "account/info" [] (mkHandler handler)+    return $ mergeLefts result+    where+        handler 200 _ body = handleJsonBody body+        handler code reason body = Left $ "non-200 response from Dropbox (" ++ (show code) ++ ":" ++ reason ++ ": " ++ (show body) ++ ")"++----------------------------------------------------------------------+-- Metadata JSON++-- |The metadata for a file or folder.  'MetaBase' contains the metadata common to+-- files and folders.  'MetaExtra' contains the file-specific or folder-specific data.+data Meta = Meta MetaBase MetaExtra+    deriving (Eq, Show)++-- |Metadata common to both files and folders.+data MetaBase = MetaBase+    { metaRoot :: AccessType  -- ^Matches the 'AccessType' of the app that retrieved the metadata.+    , metaPath :: String      -- ^The full path (starting with a "/") of the file or folder, relative to 'metaRoot'+    , metaIsDeleted :: Bool   -- ^Whether this metadata entry refers to a file that had been deleted when the entry was retrieved.+    , metaThumbnail :: Bool   -- ^Will be @True@ if this file might have a thumbnail, and @False@ if it definitely doesn't.+    , metaIcon :: String      -- ^The name of the icon used to illustrate this file type in Dropbox's icon library (<https://www.dropbox.com/static/images/dropbox-api-icons.zip>).+    } deriving (Eq, Show)++-- |Metadata that's specific to either files or folders.+data MetaExtra+    = File FileExtra    -- ^Files have additional metadata+    | Folder            -- ^Folders do not+    deriving (Eq, Show)++-- |Represents a file's revision ('fileRevision').+newtype FileRevision = FileRevision String deriving (Eq, Show)+fileRevisionToString (FileRevision s) = s++-- |Extra metadata specific to files (and not folders)+data FileExtra = FileExtra+    { fileBytes :: Integer         -- ^The file size (bytes)+    , fileHumanSize :: String      -- ^A human-readable representation of the file size, for example "15 bytes" (localized according to 'Locale' in 'Config')+    , fileRevision :: FileRevision -- ^The revision of the file+    , fileModified :: UTCTime      -- ^When this file was added or last updated+    } deriving (Eq, Show)++-- |Represents an identifier for a folder's metadata and contents.  Can be used with+-- 'getMetadataWithChildrenIfChanged' to avoid downloading a folder's metadata and contents+-- if it hasn't changed.+newtype FolderHash = FolderHash String deriving (Eq, Show)+folderHashToString (FolderHash s) = s++-- |The single-level contents of a folder.+data FolderContents = FolderContents+    { folderHash :: FolderHash  -- ^An identifier for the folder's metadata and contents.+    , folderChildren :: [Meta]  -- ^The metadata for the immediate children of a folder.+    } deriving (Eq, Show)++-- Used internally to parse out a metadata for a folder that also includes a child list.+newtype MetaWithChildren = MetaWithChildren (Meta, Maybe FolderContents)+removeMetaChildren (MetaWithChildren (meta, _)) = meta+addMetaChildren meta = MetaWithChildren (meta, Nothing)++instance JSON Meta where+    showJSON = showJSON.addMetaChildren+    readJSON = (liftM removeMetaChildren).readJSON++instance JSON MetaWithChildren where+    showJSON (MetaWithChildren (Meta base extra, maybeContents)) = JSON.makeObj (baseFields ++ extraFields ++ contentsFields)+        where+            baseFields =+                [ ("root", showJSON $ accessTypeRoot $ metaRoot base)+                , ("path", showJSON $ metaPath base)+                , ("is_deleted", showJSON $ metaIsDeleted base)+                , ("thumb_exists", showJSON $ metaThumbnail base)+                , ("icon", showJSON $ metaIcon base)+                ]+            extraFields = case extra of+                File f ->+                    [ ("bytes", showJSON $ fileBytes f)+                    , ("size", showJSON $ fileHumanSize f)+                    , ("rev", showJSON $ fileRevisionToString $ fileRevision f)+                    , ("modified", showJSON $ dbFormatTime $ fileModified f)+                    ]+                Folder -> []+            contentsFields = case maybeContents of+                Just fc ->+                    [ ("hash", showJSON $ folderHashToString $ folderHash fc)+                    , ("contents", showJSON $ map addMetaChildren (folderChildren fc))+                    ]+                Nothing -> []++    readJSON (JSON.JSObject obj) = do+        rootStr :: String <- readJsonField "root" m+        root <- case rootStr of+            "app_folder" -> return AccessTypeAppFolder+            "dropbox" -> return AccessTypeDropbox+            _ -> fail ("expecting \"app_folder\" or \"dropbox\", instead got: " ++ show rootStr)+        path <- readJsonField "path" m+        isDeleted <- readJsonFieldD "is_deleted" m False+        thumbnail <- readJsonField "thumb_exists" m+        icon <- readJsonField "icon" m+        let base = MetaBase {+              metaRoot = root+            , metaPath = path+            , metaIsDeleted = isDeleted+            , metaThumbnail = thumbnail+            , metaIcon = icon+            }+        isFolder <- readJsonField "is_dir" m+        (extra, contents) <- if isFolder+            then do+                hash <- readJsonFieldD "hash" m ""+                children <- readJsonFieldD "contents" m []+                return $ (Folder, Just FolderContents+                    { folderHash = FolderHash hash+                    , folderChildren = (map removeMetaChildren children)+                    })+            else do+                bytes <- readJsonField "bytes" m+                humanSize <- readJsonField "size" m+                revision <- readJsonField "rev" m+                modifiedStr <- readJsonField "modified" m+                modified <- case dbParseTime modifiedStr of+                    Just utcTime -> return utcTime+                    Nothing -> fail "invalid date/time format"+                return $ (File FileExtra+                    { fileBytes = bytes+                    , fileHumanSize = humanSize+                    , fileRevision = FileRevision revision+                    , fileModified = modified+                    }, Nothing)+        return $ MetaWithChildren (Meta base extra, contents)+        where+            m = JSON.fromJSObject obj+    readJSON _ = fail "expecting an object"++----------------------------------------------------------------------+-- GetMetadata++-- |Get the metadata for the file or folder at the given path.+getMetadata ::+    Manager    -- ^The HTTP connection manager to use.+    -> Session+    -> Path      -- ^The full path (relative to your 'DbAccessType' root)+    -> IO (Either ErrorMessage Meta)+getMetadata mgr session path = do+    if "/" `isPrefixOf` path+        then do+            result <- doGet mgr session hostsApi url params (mkHandler handler)+            return $ mergeLefts result+        else return $ Left $ "path must start with \"/\""+    where+        at = accessTypePath $ configAccessType (sessionConfig session)+        url = "metadata/" ++ at ++ path+        params = [("list", "false")]+        handler 200 _ body = handleJsonBody body+        handler code reason body = Left $ "non-200 response from Dropbox (" ++ (show code) ++ ":" ++ reason ++ ": " ++ (show body) ++ ")"++-- |Get the metadata for the file or folder at the given path.  If it's a folder,+-- return the first-level folder contents' metadata entries as well.+getMetadataWithChildren ::+    Manager    -- ^The HTTP connection manager to use.+    -> Session+    -> Path      -- ^The full path (relative to your 'DbAccessType' root)+    -> Maybe Integer+        -- ^A limit on folder contents (max: 10,000).  If the path refers to a folder and this folder+        -- has more than the specified number of immediate children, the entire+        -- 'getMetadataWithChildren' call will fail with an HTTP 406 error code.  If unspecified, or+        -- if set to zero, the server will set this to 10,000.+    -> IO (Either ErrorMessage (Meta, Maybe FolderContents))+getMetadataWithChildren mgr session path childLimit = do+    if "/" `isPrefixOf` path+        then do+            result <- doGet mgr session hostsApi url params (mkHandler handler)+            return $ mergeLefts result+        else return $ Left $ "'path' must start with \"/\""+    where+        at = accessTypePath $ configAccessType (sessionConfig session)+        url = "metadata/" ++ at ++ path+        params = [("list", "true")] ++ case childLimit of+            Just l -> [("file_limit", show l)]+            Nothing -> []+        handler 200 _ body = handleJsonBodyT (\(MetaWithChildren v) -> v) body+        handler code reason body = Left $ "non-200 response from Dropbox (" ++ (show code) ++ ":" ++ reason ++ ": " ++ (show body) ++ ")"++-- |Same as 'getMetadataWithChildren' except it'll return @Nothing@ if the 'FolderHash'+-- of the folder on Dropbox is the same as the 'FolderHash' passed in.+getMetadataWithChildrenIfChanged ::+    Manager           -- ^The HTTP connection manager to use.+    -> Session+    -> Path+    -> Maybe Integer+    -> FolderHash+        -- ^For folders, the returned child metadata will include a 'folderHash' field that+        -- is a short identifier for the current state of the folder.  If the 'FolderHash'+        -- for the specified path hasn't change, this call will return @Nothing@, which+        -- indicates that the previously-retrieved metadata is still the latest.+    -> IO (Either ErrorMessage (Maybe (Meta, Maybe FolderContents)))+getMetadataWithChildrenIfChanged mgr session path childLimit (FolderHash hash) = do+    if "/" `isPrefixOf` path+        then do+            result <- doGet mgr session hostsApi url params (mkHandler handler)+            return $ mergeLefts result+        else return $ Left $ "'path' must start with \"/\""+    where+        at = accessTypePath $ configAccessType (sessionConfig session)+        url = "metadata/" ++ at ++ path+        params = [("list", "true"), ("hash", hash)] ++ case childLimit of+            Just l -> [("file_limit", show l)]+            Nothing -> []+        handler 200 _ body = handleJsonBodyT (\(MetaWithChildren v) -> Just v) body+        handler 304 _ _ = Right Nothing+        handler code reason body = Left $ "non-200 response from Dropbox (" ++ (show code) ++ ":" ++ reason ++ ": " ++ (show body) ++ ")"++----------------------------------------------------------------------+-- AddFile/ForceFile/UpdateFile++-- |Add a new file.  If a file or folder already exists at the given path, your+-- file will be automatically renamed.  If successful, you'll get back the metadata+-- for your newly-uploaded file.+addFile ::+    Manager    -- ^The HTTP connection manager to use.+    -> Session+    -> Path        -- ^The full path (relative to your 'DbAccessType' root)+    -> RequestBody -- ^The file contents.+    -> IO (Either ErrorMessage Meta)+addFile mgr session path contents = putFile mgr session path contents [("overwrite", "false")]++-- |Overwrite a file, assuming it is the version you expect.  Specify the version+-- you expect with the 'FileRevision'.  If the file on Dropbox matches the given+-- revision, the file will be replaced with the contents you specify.  If the file+-- on Dropbox doesn't have the specified revision, it will be left alone and your+-- file will be automatically renamed.  If successful, you'll get back the metadata+-- for your newly-uploaded file.+updateFile ::+    Manager    -- ^The HTTP connection manager to use.+    -> Session+    -> Path         -- ^The full path (relative to your 'DbAccessType' root)+    -> RequestBody  -- ^The file contents.+    -> FileRevision -- ^The revision of the file you expect to be writing to.+    -> IO (Either ErrorMessage Meta)+updateFile mgr session path contents (FileRevision rev) =+    putFile mgr session path contents [("parent_rev", rev)]++-- |Add a file.  If a file already exists at the given path, that file will+-- be overwritten.  If successful, you'll get back the metadata for your+-- newly-uploaded file.+forceFile ::+    HE.Manager     -- ^The 'Network.HTTP.Enumerator.Manager' to use.+    -> Session+    -> Path        -- ^The full path (relative to your 'DbAccessType' root)+    -> RequestBody -- ^The file contents.+    -> IO (Either ErrorMessage Meta)+forceFile mgr session path contents = putFile mgr session path contents [("overwrite", "true")]++----------------------------------------------------------------------+-- The underlying "put_file" call.++putFile ::+    HE.Manager+    -> Session+    -> Path+    -> RequestBody+    -> [(String,String)]+    -> IO (Either ErrorMessage Meta)+putFile mgr session path contents params =+    if "/" `isPrefixOf` path+        then do+            result <- doPut mgr session hostsApiContent url params contents (mkHandler handler)+            return $ mergeLefts result+        else return $ Left $ "path must start with \"/\""+    where+        at = accessTypePath $ configAccessType (sessionConfig session)+        url = "files_put/" ++ at ++ path+        handler 200 _ body = handleJsonBody body+        handler code reason body = Left $ "non-200 response from Dropbox (" ++ (show code) ++ ":" ++ reason ++ ": " ++ (show body) ++ ")"++----------------------------------------------------------------------++-- very low level uri generator, handles proper escaping+generateDropboxURI' :: Bool -> String -> String -> Int -> String -> [(String, String)] -> String+generateDropboxURI' escapePath proto host port path params = URI.uriToString id (URLEncoded.addToURI (URLEncoded.importList params) (URI.URI proto (Just $ URI.URIAuth "" host $ ":" ++ show port) path' "" "")) ""+  where path' = if escapePath then (URI.escapeURIString URI.isAllowedInURI path) else path++prepRequest :: Session -> (Hosts -> String) -> String -> [(String, String)] -> (String, String)+prepRequest (Session config (AccessToken atKey atSecret)) hostSelector path params = (uri, oauthHeader)+    where+        host = hostSelector (configHosts config)+        (Locale locale) = configUserLocale config+        consumerPair = configAppId config+        uri = generateDropboxURI' False "https:" host 443 ("/" ++ apiVersion ++ "/" ++ path) (("locale", locale) : params)+        oauthHeader = buildOAuthHeader consumerPair (atKey, atSecret)++doPut ::+    Manager+    -> Session+    -> (Hosts -> String)+    -> String+    -> [(String,String)]+    -> RequestBody+    -> Handler r+    -> IO (Either ErrorMessage r)+doPut mgr session hostSelector path params requestBody handler = do+    let (uri, oauthHeader) = prepRequest session hostSelector path params+    let vf = certVerifierFunc $ configCertVerifier $ sessionConfig session+    httpClientPut mgr vf uri oauthHeader handler requestBody++doGet ::+    Manager+    -> Session+    -> (Hosts -> String)+    -> String+    -> [(String,String)]+    -> Handler r+    -> IO (Either ErrorMessage r)+doGet mgr session hostSelector path params handler = do+    let (uri, oauthHeader) = prepRequest session hostSelector path params+    let vf = certVerifierFunc $ configCertVerifier $ sessionConfig session+    httpClientGet mgr vf uri oauthHeader handler++----------------------------------------------------------------------++type Manager = HE.Manager++withManager :: (Manager -> IO r) -> IO r+withManager = HE.withManager++----------------------------------------------------------------------++type SimpleHandler r = Int -> String -> ByteString -> r++-- |HTTP response-handling function.+type Handler r = HT.Status -> HT.ResponseHeaders -> (Iteratee ByteString IO r)++-- |An HTTP request body: an 'Int64' for the length and an 'Enumerator'+-- that yields the actual data.+data RequestBody = RequestBody Int64 (forall r. Enumerator ByteString IO r)++-- |Create a 'RequestBody' from a single 'ByteString'+bsRequestBody :: ByteString -> RequestBody+bsRequestBody bs = RequestBody length (E.enumLists [[bs]])+    where+        length = fromInteger $ toInteger $ BS.length bs++mkHandler :: SimpleHandler r -> Handler r+mkHandler sh (HT.Status code reason) _headers = do+    bs <- bsIteratee+    return $ sh code (BS8.unpack reason) bs++mergeLefts :: Either a (Either a b) -> Either a b+mergeLefts v = case v of+    Left a -> Left a+    Right r -> r++-- |An 'Iteratee' that reads in 'ByteString' chunks and constructs one concatenated 'ByteString'+bsIteratee :: Monad m => Iteratee ByteString m ByteString+bsIteratee = do+    chunks <- EL.consume+    return $ BS.concat chunks++httpClientDo ::+    Manager+    -> HT.Ascii+    -> RequestBody+    -> CertVerifierFunc+    -> URL+    -> String+    -> Handler r+    -> IO (Either String r)+httpClientDo mgr method (RequestBody len bsEnum) vf url oauthHeader handler =+    case HE.parseUrl url of+        Just baseReq -> do+            let req = baseReq {+                HE.secure = True,+                HE.method = method,+                HE.requestHeaders = headers,+                HE.requestBody = HE.RequestBodyEnum len builderEnum,+                HE.checkCerts = vf }+            resp <- E.run_ $ HE.http req handler mgr+            return $ Right resp+        Nothing -> do+            return $ Left $ "bad URL: " ++ show url+    where+        headers = [("Authorization", UTF8.fromString oauthHeader)]+        builderEnum = E.joinE bsEnum (EL.map BlazeBS.fromByteString)++httpClientGet :: Manager -> CertVerifierFunc -> URL -> String -> Handler r -> IO (Either String r)+httpClientGet mgr vf url oauthHeader handler = httpClientDo mgr "GET" (bsRequestBody BS.empty) vf url oauthHeader handler++httpClientPut :: Manager -> CertVerifierFunc -> URL -> String -> Handler r -> RequestBody -> IO (Either String r)+httpClientPut mgr vf url oauthHeader handler requestBody = httpClientDo mgr "PUT" requestBody vf url oauthHeader handler
+ dropbox-sdk.cabal view
@@ -0,0 +1,39 @@+Name: dropbox-sdk+Version: 0.1.0+Synopsis: A library to access the Dropbox HTTP API.+Description:+    The Dropbox HTTP API provides ways for applications to access+    users' Dropbox accounts programmatically.+License: MIT+License-File: License.txt+Author: Kannan Goundan <kannan@cakoose.com>+Maintainer: Kannan Goundan <kannan@cakoose.com>+Category: Network APIs+Build-Type: Simple+Cabal-Version: >= 1.10+Data-Files: trusted-certs.crt++Library+    Build-Depends:+        base         == 4.*,+        bytestring   == 0.9.*,+        HTTP         == 4000.*,+        utf8-string  == 0.3.*,+        urlencoded   == 0.3.*,+        json         == 0.4.*,+        time         == 1.4.*,+        old-locale   == 1.0.*,+        network      == 2.3.*,+        enumerator   >= 0.4.16 && < 0.5,+        http-types   >= 0.6.5 && < 0.7,+        http-enumerator  >= 0.7.2.1 && < 0.8,+        blaze-builder    == 0.3.*,+        tls          == 0.8.*,+        tls-extra    == 0.4.*,+        certificate  == 1.*+    HS-Source-Dirs: Source+    Exposed-Modules: Dropbox+    Other-Modules: Paths_dropbox_sdk+    GHC-Options: -Wall -fno-warn-missing-signatures -fno-warn-name-shadowing+    Default-Language: Haskell2010+    Default-Extensions: OverloadedStrings, ScopedTypeVariables, Rank2Types
+ trusted-certs.crt view
@@ -0,0 +1,341 @@+#        Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com <server-certs@thawte.com>+#        Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress= server-certs@thawte.com+-----BEGIN CERTIFICATE-----+MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx+FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD+VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv+biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm+MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx+MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT+DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3+dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl+cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3+DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD+gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91+yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX+L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj+EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG+7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e+QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ+qdq5snUb9kLy78fyGPmJvKP/iiMucEc=+-----END CERTIFICATE-----+#        Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com <premium-server@thawte.com>+#        Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com <premium-server@thawte.com>+-----BEGIN CERTIFICATE-----+MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx+FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD+VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv+biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy+dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t+MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB+MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG+A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp+b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl+cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE+VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ+ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR+uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG+9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI+hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM+pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==+-----END CERTIFICATE-----+#        Subject: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority+#        Issuer: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority+-----BEGIN CERTIFICATE-----+MIICPDCCAaUCEDJQM89Q0VbzXIGtZVxPyCUwDQYJKoZIhvcNAQECBQAwXzELMAkG+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz+cyAxIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2+MDEyOTAwMDAwMFoXDTIwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAxIFB1YmxpYyBQcmlt+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN+ADCBiQKBgQDlGb9to1ZhLZlIcfZn3rmN67eehoAKkQ76OCWvRoiC5XOooJskXQ0f+zGVuDLDQVoQYh5oGmxChc9+0WDlrbsH2FdWoqD+qEgaNMax/sDTXjzRniAnNFBHi+TkVWaR94AoDa3EeRKbs2yWNcxeDXLYd7obcysHswuiovMaruo2fa2wIDAQABMA0G+CSqGSIb3DQEBAgUAA4GBAEtEZmBoZOSYG/OwcuaViXzde7OVwB0u2NgZ0C00PcZQ+mhCGjKo/O6gE/DdSlcPZydvN8oYGxLEb8IKIMEKOF1AcZHq4PplJdJf8rAJD+5YM+VgQlDHx8h50kp9jwMim1pN9dokzFFjKoQvZFprY2ueC/ZTaTwtLXa9zeWdaiNfhF+-----END CERTIFICATE-----+#        Subject: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority+#        Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority+-----BEGIN CERTIFICATE-----+MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz+cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2+MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN+ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE+BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is+I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G+CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do+lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc+AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k+-----END CERTIFICATE-----+#        Subject: C=US, O=RSA Data Security, Inc., OU=Secure Server Certification Authority+#        Issuer: C=US, O=RSA Data Security, Inc., OU=Secure Server Certification Authority+-----BEGIN CERTIFICATE-----+MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzELMAkG+A1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD+VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk0+MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxIDAeBgNV+BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2Vy+dmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGbMA0GCSqGSIb3DQEBAQUAA4GJ+ADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6OLDfO6zV4ZFQD5YRAUcm/jwjiioII+0haGN1XpsSECrXZogZoFokvJSyVmIlZsiAeP94FZbYQHZXATcXY+m3dM41CJVphI+uR2nKRoTLkoRWZweFdVJVCxzOmmCsZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZI+hvcNAQECBQADfgBl3X7hsuyw4jrg7HFGmhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3+YQO2WxZpO8ZECAyIUwxrl0nHPjXcbLm7qt9cuzovk2C2qUtN8iD3zV9/ZHuO3ABc+1/p3yjkWWW8O6tO1g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA==+-----END CERTIFICATE-----+#        Subject: C=US, O=Equifax Secure Inc., CN=Equifax Secure Global eBusiness CA-1+#        Issuer: C=US, O=Equifax Secure Inc., CN=Equifax Secure Global eBusiness CA-1+-----BEGIN CERTIFICATE-----+MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc+MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT+ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw+MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj+dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l+c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC+UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc+58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/+o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH+MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr+aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA+A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA+Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv+8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV+-----END CERTIFICATE-----+#        Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware+#        Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware+-----BEGIN CERTIFICATE-----+MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt+SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG+A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe+MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v+d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh+cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn+0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ+M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a+MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd+oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI+DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy+oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD+VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0+dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy+bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF+BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM+//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli+CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE+CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t+3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS+KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==+-----END CERTIFICATE-----+#        Subject: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority+#        Issuer: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority+-----BEGIN CERTIFICATE-----+MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi+MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu+MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp+dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV+UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO+ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz+c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP+OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl+mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF+BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4+qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw+gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB+BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu+bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp+dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8+6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/+h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH+/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv+wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN+pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey+-----END CERTIFICATE-----+#        Subject: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority+#        Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority+-----BEGIN CERTIFICATE-----+MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh+MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE+YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3+MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo+ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg+MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN+ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA+PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w+wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi+EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY+avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo++YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE+sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h+/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5+IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj+YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD+ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy+OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P+TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ+HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER+dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf+ReYNnyicsbkqWletNw+vHX/bvZ8=+-----END CERTIFICATE-----+#        Subject: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., OU=http://certificates.godaddy.com/repository<http://certificates.godaddy.com/repository>, CN=Go Daddy Secure Certification Authority/serialNumber=07969287+#        Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority+-----BEGIN CERTIFICATE-----+MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx+ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g+RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw+MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH+QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j+b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j+b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj+YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN+AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H+KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm+VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR+SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT+cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ+6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu+MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS+kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB+BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f+BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv+c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH+AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO+BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG+OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU+A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o+0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX+RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH+qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV+U+4=+-----END CERTIFICATE-----+#        Subject: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., CN=Go Daddy Root Certificate Authority - G2+#        Issuer: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., CN=Go Daddy Root Certificate Authority - G2+-----BEGIN CERTIFICATE-----+MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT+EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp+ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz+NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH+EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE+AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD+E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH+/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy+DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh+GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR+tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA+AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE+FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX+WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu+9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr+gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo+2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO+LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI+4uJEvlz36hz1+-----END CERTIFICATE-----+#        Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA+#        Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA+-----BEGIN CERTIFICATE-----+MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT+MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i+YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg+R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9+9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq+fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv+iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU+1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0++bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW+MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA+ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l+uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn+Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS+tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF+PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un+hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV+5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==+-----END CERTIFICATE-----+#        Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority+#        Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority+-----BEGIN CERTIFICATE-----+MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo+R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx+MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK+Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp+ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC+AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9+AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA+ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0+7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W+kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI+mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G+A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ+KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1+6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl+4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K+oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj+UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU+AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=+-----END CERTIFICATE-----+#        Subject: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority+#        Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com<http://www.valicert.com//emailAddress=info@valicert.com>+-----BEGIN CERTIFICATE-----+MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1Zh+bGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIElu+Yy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24g+QXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAe+BgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoX+DTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBE+YWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0+aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgC+ggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv+2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+q+N1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiO+r18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lN+f4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEH+U1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHU+TBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMb+VmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwg+SW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlv+biBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEg+MB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUw+AwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdv+ZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMu+Z29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUd+IAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNv+bS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1+QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4O+WBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0Vmsf+SxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==+-----END CERTIFICATE-----+#        Subject: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com<http://www.valicert.com//emailAddress=info@valicert.com>+#        Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com<http://www.valicert.com//emailAddress=info@valicert.com>+-----BEGIN CERTIFICATE-----+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0+IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz+BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y+aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG+9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy+NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y+azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs+YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw+Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl+cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY+dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9+WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS+v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v+UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu+IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC+W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd+-----END CERTIFICATE-----++