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 +20/−0
- Setup.hs +2/−0
- Source/Dropbox.hs +851/−0
- dropbox-sdk.cabal +39/−0
- trusted-certs.crt +341/−0
+ 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-----++