packages feed

mysql-haskell 0.8.4.3 → 1.5.0

raw patch · 142 files changed

Files

ChangeLog.md view
@@ -1,5 +1,304 @@ # Revision history for mysql-haskell +## 1.5.0 -- 2026.11.11 ++Security fixes and new functions. A major version because two behaviours+change, described below; no existing export changes its type. A+text or bytes parameter of `query`, `execute` or `executeMany` can no longer+end its string literal under `NO_BACKSLASH_ESCAPES`; the binlog dump checks+every event's CRC32 checksum when `binlog_checksum` is `CRC32`, the default,+and raises `BinLogChecksumMismatch` on a corrupted event it used to decode as+intact; MariaDB accounts identified via ed25519 can log in; and+`makeClientParams'` sends its client certificate, so a `REQUIRE X509` account+can log in. The new `resetConnection` returns a connection's session to that of+a new one without reconnecting, so a pool can hand it to its next user (MySQL+5.7.3 or MariaDB 10.2.4 and newer).++Two behaviours change. A client certificate the server cannot verify now fails+the TLS handshake, where 1.4.1 never sent it. `escapeText` and `escapeBytes`+double a quote and leave control characters as they are, so their output is+only safe between single quotes; the server stores the same values as before+in the default mode, and under `NO_BACKSLASH_ESCAPES` a backslash in a+parameter is still stored twice. On a gbk, big5, sjis, cp932 or gb18030+connection text parameters remain unsafe; use prepared statements there.++* Reset a pooled connection's session without reconnecting+  [#109](https://github.com/winterland1989/mysql-haskell/pull/109)+* Parameters escaped safely under `NO_BACKSLASH_ESCAPES`+  [#110](https://github.com/winterland1989/mysql-haskell/pull/110)+* Binlog event checksums checked instead of skipped+  [#112](https://github.com/winterland1989/mysql-haskell/pull/112)+* MariaDB accounts identified via ed25519 can log in+  [#111](https://github.com/winterland1989/mysql-haskell/pull/111)+* `makeClientParams'` sends its client certificate, so an account created+  `REQUIRE X509` can log in through `Database.MySQL.TLS`+  [#108](https://github.com/winterland1989/mysql-haskell/pull/108)++## 1.4.1 -- 2026.11.11++Faster decoding. No type changes beyond one new exception, but one change of+behaviour, described last. `query_` and+`queryStmt` decode rows with dates and text up to a third faster than 1.4.0:+a date in a common range is computed without `Integer` arithmetic, and a+string is decoded as UTF-8 once, when its value is used, instead of checked+while the row is read and decoded again later. A string whose bytes are not+UTF-8, as a connection in another charset can send, now raises+`InvalidUtf8Text` when the value is used, where 1.4.0 refused the whole row;+the decoders of `Database.MySQL.Decoder` still refuse it while reading.++* Text decoded when used, common dates without `Integer` arithmetic+  [#107](https://github.com/winterland1989/mysql-haskell/pull/107)++## 1.4.0 -- 2026.11.11++Fixes for MySQL and MariaDB alike, found comparing mysql-haskell with the+mariadb-haskell fork, and faster row decoding. A text query no longer loses a+row whose first value is 16 MiB or longer; insert IDs above 2^63 come back+intact; a `?` inside a quoted string or a comment is no longer taken for a+placeholder; dates such as `0000-00-00` round-trip instead of turning into+`0000-01-01`; latin1 results decode; FLOAT and DOUBLE values in exponent form+no longer lose their exponent through `query_` (`1e20` came back as 1); the+binlog row decoder no longer drops a statement's rows events after its first;+and the Mozilla CA bundle is installed with the library. `query_` and+`queryStmt` resolve each column's type once per result set, and the new+`Database.MySQL.Decoder` decodes rows straight into Haskell values with typed+errors through `queryRows_`, `queryRows` and `queryStmtRows`, or by column+number through `queryRawRows_` and `queryStmtRawRows`.++New in `Database.MariaDB`, for MariaDB servers only: connections that ask for+capabilities only MariaDB has. Extended column metadata names JSON, UUID, INET6+and geometry columns through `prepareStmtMetadata`, and `executeBulk` runs a+prepared statement for many parameter rows in one round trip, several times+faster than `executeMany`. Its connect functions refuse a MySQL server with+`NotMariaDB`; `mysqlConnection` gives the underlying connection for everything+in `Database.MySQL.Base`. Connections made with `Database.MySQL.Base` send the+same handshake as before.++Upgrading can break code. `okLastInsertID` is a `Word64`. `MySQLValue` gains+`MySQLDateRaw`, `MySQLDateTimeRaw` and `MySQLTimeStampRaw` for dates a `Day`+cannot hold, `DecodeError` gains `DecodeUnrepresentableDate`, and+`BinLogEventType` gains `BINLOG_UNRECOGNIZED_EVENT` and loses its `Enum`+instance (use `binLogEventTypeCode`). A connection whose reply was interrupted,+for example by a timeout, now refuses further commands with `AbandonedReply`+instead of handing the rest of that reply to the next query (#10); discard it+and reconnect. `resetStmt` with rows still unread raises `UnconsumedResultSet`,+`withTransaction` rethrows the procedure's own exception, and+`decodeRowBinLogEvent` raises `RowEventDecodeException` on an event that may+hold rows it cannot decode, such as MySQL's compressed transactions, where it+used to crash or skip it. `Database.MySQL.Base` now also exports the five new+query functions and re-exports `Database.MySQL.Protocol.RawRow`, which can clash+with same-named definitions under an unqualified import.++It needs GHC 9.6 or newer, for the new `unwitch` dependency, and bytestring+0.11.2; projects on older compilers keep resolving to 1.3.2, the last release+before this on Hackage (1.3.3 was never uploaded).++* Fix eight defects for MySQL and MariaDB, each with a reproducer+  [#102](https://github.com/winterland1989/mysql-haskell/pull/102)+* Faster row decoding, decoders straight into Haskell values, and FLOAT and+  DOUBLE in exponent form read correctly+  [#98](https://github.com/winterland1989/mysql-haskell/pull/98)+* A MariaDB module with capability negotiation and extended column metadata+  [#100](https://github.com/winterland1989/mysql-haskell/pull/100)+* MariaDB bulk execution with NULL, DEFAULT and IGNORE parameters+  [#101](https://github.com/winterland1989/mysql-haskell/pull/101)+* `Database.MariaDB` for MariaDB servers only, with `mysqlConnection` for the+  MySQL functions [#104](https://github.com/winterland1989/mysql-haskell/pull/104)+* Allow crypton 2.2 and tls 2.5+  [#105](https://github.com/winterland1989/mysql-haskell/pull/105)+* Examples for `executeBulk` and the functions that decode rows without+  `MySQLValue` [#106](https://github.com/winterland1989/mysql-haskell/pull/106)+* A comparison with the mariadb-haskell fork and a plan to adopt its+  features, in `docs/`+  [#99](https://github.com/winterland1989/mysql-haskell/pull/99)+* Benchmark figures re-measured on current hardware+  [#95](https://github.com/winterland1989/mysql-haskell/pull/95)+* A profile of where the CPU time goes, in `benchmark/`+  [#96](https://github.com/winterland1989/mysql-haskell/pull/96)++## 1.3.3 -- 2026.10.07++New functions, and fixes to `executeMany` and `executeMany_` for inputs that+left 1.3.2's connection out of step. `queryMulti_` and `queryMulti` return every+result of a CALL or a multi-statement query in order: each result set with its+columns and rows, and each statement's `OK`.++`executeMany` and `executeMany_` now read the whole reply. A statement that+returns rows raises `ExtraResultSets`, where 1.3.2 threw `UnexpectedPacket` or+left the rows for the next command. A query with several statements per+parameter set gives one `OK` per statement, where 1.3.2 returned one per+parameter set and left the rest. `executeMany` with no parameter sets returns+`[]` without contacting the server, where 1.3.2 sent an empty query whose error+reached the next command.+++ Every result set of a CALL or multi-statement query through `queryMulti_`+  [#94](https://github.com/winterland1989/mysql-haskell/pull/94)++## 1.3.2 -- 2026.10.07++Requires crypton 2.0 or newer, the first crypton with the timing side-channel+fixes that TLS connections reach; a project held to crypton 1.x keeps+resolving to 1.3.1. The only API addition is the `ExtraResultSets` exception.+The query functions now return an empty result for an INSERT, UPDATE or DELETE+instead of hanging. `execute_`, `executeStmt` and the query functions read the+whole reply of a CALL or a multi-statement query, so they no longer leave+results behind for the next command; `executeMany` and `executeMany_` are+unchanged (#93).++Errors change only for statements that produce several results, or rows where+`execute_` and `executeStmt` expect none. Those two raise `ExtraResultSets` for+a result set (1.3.1 threw `UnexpectedPacket` when it came first and returned the+`OK` when it came later); the query functions raise it after the first result+set's rows when a second one follows, which 1.3.1 left on the connection. A+later statement that fails raises its `ERRException` from that call or its row+stream, also ahead of `ExtraResultSets`. Stack users who set the removed+`crypton-1-1` flag must delete it from `stack.yaml`, since Stack rejects flags a+package does not define.+++ Query functions no longer hang on statements without a result set,+  multi-result replies are read to the end, and crypton 2 is required+  [#91](https://github.com/winterland1989/mysql-haskell/pull/91)++ CI: the cabal job builds and runs the test suite+  [#90](https://github.com/winterland1989/mysql-haskell/pull/90)++## 1.3.1 -- 2026.10.06++ Allow crypton 2.0 and 2.1 (commercialhaskell/stackage#8128). The library+  code needed no change; CI now builds and tests against every crypton+  series the bounds admit (0.34, 1.0, 1.1, 2.0 and 2.1).++## 1.3.0 -- 2026.07.22++ Add `Database.MySQL.Field` with a `Field` typeclass for converting+  individual Haskell values to and from `MySQLValue` (#86). One combined+  class in the style of persistent's `PersistField`, with a `DecodeError`+  sum type for decode failures and named top-level encode/decode functions+  backing every instance.++## 1.2.5 -- 2026.05.04++ Replace abandoned `pem` dependency with `crypton-pem` (#84)++## 1.2.4 -- 2026.04.20++ Fix benchmark compilation failures (#82):+  + Replace deleted `Data.Binary.Parser.Char8` import in benchmark with+    `Word8`-based combinators from `Data.Binary.Parser`+  + Replace `GHC.Prim`/`GHC.Types` imports with `GHC.Exts` from `base`+  + Fix `Word8#` vs `Word#` type mismatch in `mhDigitToInt`+  + Remove stale haddock reference to deleted `Char8` module++## 1.2.3 -- 2026.04.12++ Support caching_sha2_password full auth via RSA on non-TLS connections#81 thanks @ikaro1192++## 1.2.2 -- 2026.03.25++ Widen tls upper bound to allow tls 2.4.x++ Fix unsafe ByteString operations (`unsafeDrop`, `unsafeTail`, `unsafeIndex`)+  on untrusted wire protocol data that caused undefined behavior (segfaults,+  garbage reads) on malformed input. The safe alternatives are also O(1).+  Affected parsers: text protocol timestamp/datetime/time/date fields,+  NEWDECIMAL in binlog, and `eventHeaderLen` for unknown event types.++## 1.2.1 -- 2026.03.23++ Export `connectUnixSocket` function.++ Tweak cabal file to better handle crypton vs cryptonite.++ Fix version cut off for tls compatibility.++ Fix missing `liftA2` import when running tests on GHC 9.4.++ Add integration test for Unix socket.++## 1.2.0 -- 2026.03.07++ Add support for  caching_sha2_password authentiation.++## 1.1.9 -- 2026.03.07++ Fix binary protocol error 1210 on modern MySQL/MariaDB:+  `MYSQL_TYPE_BIT` and `MYSQL_TYPE_YEAR` are not valid parameter types+  for `COM_STMT_EXECUTE`. Send `MySQLBit` as `MYSQL_TYPE_LONGLONG`+  (unsigned, little-endian) and `MySQLYear` as `MYSQL_TYPE_SHORT`+  (unsigned, 2-byte integer) instead.++ Fix `MySQLYear` binary encoding: was sending a length-encoded string+  which caused data misalignment for all subsequent parameters.++ Fix version detection for MariaDB 10+/11+ (fractional seconds support).++ Fix CI: enable binary logging and grant binlog privileges for+  integration tests on MariaDB.++ Add roundtrip integration tests for `MySQLBit` and `MySQLYear`.++## 1.1.8++ bump constraints++## 1.1.7 -- 2025.08.23 ++ bump constraints++ upgrade cabal file++ add upper bounds to all the warnings+  + it was only the test suite so I made them broad.++ Disable inline-rule-shadowing for Int24 and Word24.+  those rules fire fine. you can test it our yourself with+  the Main.hs in the main repository.+  There is a stanza to do so in cabal.++ clean up cabal file a bit, use GHC2021++## 1.1.6 -- 2024.10.23 ++ bump constraints++## 1.1.5 -- 2024.06.25++ bump constraints++## 1.1.4 -- 2024.02.17 ++ bump constraints++## 1.1.3 -- 2023.08.30++ bump constraints++## 1.1.2 -- 2023.08.14+++ Fix package name of changelog++ Drop support for RC4 chipher which is depracated++ drop dependency on binary-ieee754, which was unused.++ Fix text 2 support, thanks @RikvanToor++## 1.1.1 -- 2023.08.14+++ cleaned up some warnings++ Merge back into mysql-haskell after gaining hackage access.++ Deprecate mysql-pure in favor of old hackage+  since it's only been out for a day this sort off+  stream lines upgrading for most applications.+  Cabal will just figure it out, rather then+  users having to "find" mysql-pure.+  I'll just make a bonus announcement to+  let people not depend on mysql-pure.++## 1.1.0 -- 2023.08.12 +There was a bunch of stuff unrelated to mysql+which I purged.+If you need any on these go depend on the +respective unmaintained package.+++ Delete module System.IO.Streams.UnixSocket++ Dleete module Data.Binary.Parser.Char8++ Delete module System.IO.Streams.Binary++## 1.0.2 -- 2023.08.12 ++ Bump dependencies, go all into crypton++ merge tcp-streams into the package++## 1.0.1 -- 2023.08.12 ++ add json testfiles as extra source files to make tests pass in nix builds++## 1.0.0 -- 2023.08.12 +++ Fork from mysql-haskell into mysql-pure++ add flake++ merge packages:+  + word24+  + binary-parsers+  + wirestreams+  +  This involved copying over all source files,+  furthermore I copied in all tests and benchmarks.+  The tests are now one giant test suite.+  I temporarly disabled the mysql tests as they need a mysql+  database to run which won't work nicely with CI right now.+  However you can run these locally by uncommenting that line.++ Add CI which relies on native cabal instead of stack++ Add an action to automatically bump version.++ Add nightly build cron job.+ ## 0.8.4.3 -- 2020-11-04  * Fix build with GHC 8.8.
− Database/MySQL/Base.hs
@@ -1,326 +0,0 @@-{-|-Module      : Database.MySQL.Base-Description : Prelude of mysql-haskell-Copyright   : (c) Winterland, 2016-License     : BSD-Maintainer  : drkoster@qq.com-Stability   : experimental-Portability : PORTABLE--This module provide common MySQL operations,--NOTEs on 'Exception's: This package use 'Exception' to deal with unexpected situations,-but you shouldn't try to catch them if you don't have a recovery plan,-for example: there's no meaning to catch a 'ERRException' during authentication unless you want to try different passwords.-By using this library you will meet:--    * 'NetworkException':  underline network is broken.-    * 'UnconsumedResultSet':  you should consume previous resultset before sending new command.-    * 'ERRException':  you receive a 'ERR' packet when you shouldn't.-    * 'UnexpectedPacket':  you receive a unexpected packet when you shouldn't.-    * 'DecodePacketException': there's a packet we can't decode.-    * 'WrongParamsCount': you're giving wrong number of params to 'renderParams'.--Both 'UnexpectedPacket' and 'DecodePacketException' may indicate a bug of this library rather your code, so please report!---}-module Database.MySQL.Base-    ( -- * Setting up and control connection-      MySQLConn-    , ConnectInfo(..)-    , defaultConnectInfo-    , defaultConnectInfoMB4-    , connect-    , connectDetail-    , close-    , ping-      -- * Direct query-    , execute-    , executeMany-    , executeMany_-    , execute_-    , query_-    , queryVector_-    , query-    , queryVector-      -- * Prepared query statement-    , prepareStmt-    , prepareStmtDetail-    , executeStmt-    , queryStmt-    , queryStmtVector-    , closeStmt-    , resetStmt-      -- * Helpers-    , withTransaction-    , QueryParam(..)-    , Param (..)-    , Query(..)-    , renderParams-    , command-    , Stream.skipToEof-      -- * Exceptions-    , NetworkException(..)-    , UnconsumedResultSet(..)-    , ERRException(..)-    , UnexpectedPacket(..)-    , DecodePacketException(..)-    , WrongParamsCount(..)-      -- * MySQL protocol-    , module  Database.MySQL.Protocol.Auth-    , module  Database.MySQL.Protocol.Command-    , module  Database.MySQL.Protocol.ColumnDef-    , module  Database.MySQL.Protocol.Packet-    , module  Database.MySQL.Protocol.MySQLValue-    ) where--import           Control.Applicative-import           Control.Exception                  (mask, onException, throwIO)-import           Control.Monad-import qualified Data.ByteString.Lazy               as L-import           Data.IORef                         (writeIORef)-import           Database.MySQL.Connection-import           Database.MySQL.Protocol.Auth-import           Database.MySQL.Protocol.ColumnDef-import           Database.MySQL.Protocol.Command-import           Database.MySQL.Protocol.MySQLValue-import           Database.MySQL.Protocol.Packet--import           Database.MySQL.Query-import           System.IO.Streams                  (InputStream)-import qualified System.IO.Streams                  as Stream-import qualified Data.Vector                        as V-------------------------------------------------------------------------------------- | Execute a MySQL query with parameters which don't return a result-set.------ The query may contain placeholders @?@, for filling up parameters, the parameters--- will be escaped before get filled into the query, please DO NOT enable @NO_BACKSLASH_ESCAPES@,--- and you should consider using prepared statement if this's not an one shot query.----execute :: QueryParam p => MySQLConn -> Query -> [p] -> IO OK-execute conn qry params = execute_ conn (renderParams qry params)--{-# SPECIALIZE execute :: MySQLConn -> Query -> [MySQLValue] -> IO OK #-}-{-# SPECIALIZE execute :: MySQLConn -> Query -> [Param]      -> IO OK #-}---- | Execute a multi-row query which don't return result-set.------ Leverage MySQL's multi-statement support to do batch insert\/update\/delete,--- you may want to use 'withTransaction' to make sure it's atomic, and--- use @sum . map okAffectedRows@ to get all affected rows count.------ @since 0.2.0.0----executeMany :: QueryParam p => MySQLConn -> Query -> [[p]] -> IO [OK]-executeMany conn@(MySQLConn is os _ _) qry paramsList = do-    guardUnconsumed conn-    let qry' = L.intercalate ";" $ map (fromQuery . renderParams qry) paramsList-    writeCommand (COM_QUERY qry') os-    mapM (\ _ -> waitCommandReply is) paramsList--{-# SPECIALIZE executeMany :: MySQLConn -> Query -> [[MySQLValue]] -> IO [OK] #-}-{-# SPECIALIZE executeMany :: MySQLConn -> Query -> [[Param]]      -> IO [OK] #-}---- | Execute multiple querys (without param) which don't return result-set.------ This's useful when your want to execute multiple SQLs without params, e.g. from a--- SQL dump, or a table migration plan.------ @since 0.8.4.0----executeMany_ :: MySQLConn -> Query -> IO [OK]-executeMany_ conn@(MySQLConn is os _ _) qry = do-    guardUnconsumed conn-    writeCommand (COM_QUERY (fromQuery qry)) os-    waitCommandReplys is---- | Execute a MySQL query which don't return a result-set.----execute_ :: MySQLConn -> Query -> IO OK-execute_ conn (Query qry) = command conn (COM_QUERY qry)---- | Execute a MySQL query which return a result-set with parameters.------ Note that you must fully consumed the result-set before start a new query on--- the same 'MySQLConn', or an 'UnconsumedResultSet' will be thrown.--- if you want to skip the result-set, use 'Stream.skipToEof'.----query :: QueryParam p => MySQLConn -> Query -> [p] -> IO ([ColumnDef], InputStream [MySQLValue])-query conn qry params = query_ conn (renderParams qry params)--{-# SPECIALIZE query :: MySQLConn -> Query -> [MySQLValue] -> IO ([ColumnDef], InputStream [MySQLValue]) #-}-{-# SPECIALIZE query :: MySQLConn -> Query -> [Param]      -> IO ([ColumnDef], InputStream [MySQLValue]) #-}---- | 'V.Vector' version of 'query'.------ @since 0.5.1.0----queryVector :: QueryParam p => MySQLConn -> Query -> [p] -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))-queryVector conn qry params = queryVector_ conn (renderParams qry params)--{-# SPECIALIZE queryVector :: MySQLConn -> Query -> [MySQLValue] -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue)) #-}-{-# SPECIALIZE queryVector :: MySQLConn -> Query -> [Param]      -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue)) #-}---- | Execute a MySQL query which return a result-set.----query_ :: MySQLConn -> Query -> IO ([ColumnDef], InputStream [MySQLValue])-query_ conn@(MySQLConn is os _ consumed) (Query qry) = do-    guardUnconsumed conn-    writeCommand (COM_QUERY qry) os-    p <- readPacket is-    if isERR p-    then decodeFromPacket p >>= throwIO . ERRException-    else do-        len <- getFromPacket getLenEncInt p-        fields <- replicateM len $ (decodeFromPacket <=< readPacket) is-        _ <- readPacket is -- eof packet, we don't verify this though-        writeIORef consumed False-        rows <- Stream.makeInputStream $ do-            q <- readPacket is-            if  | isEOF q  -> writeIORef consumed True >> return Nothing-                | isERR q  -> decodeFromPacket q >>= throwIO . ERRException-                | otherwise -> Just <$> getFromPacket (getTextRow fields) q-        return (fields, rows)---- | 'V.Vector' version of 'query_'.------ @since 0.5.1.0----queryVector_ :: MySQLConn -> Query -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))-queryVector_ conn@(MySQLConn is os _ consumed) (Query qry) = do-    guardUnconsumed conn-    writeCommand (COM_QUERY qry) os-    p <- readPacket is-    if isERR p-    then decodeFromPacket p >>= throwIO . ERRException-    else do-        len <- getFromPacket getLenEncInt p-        fields <- V.replicateM len $ (decodeFromPacket <=< readPacket) is-        _ <- readPacket is -- eof packet, we don't verify this though-        writeIORef consumed False-        rows <- Stream.makeInputStream $ do-            q <- readPacket is-            if  | isEOF q  -> writeIORef consumed True >> return Nothing-                | isERR q  -> decodeFromPacket q >>= throwIO . ERRException-                | otherwise -> Just <$> getFromPacket (getTextRowVector fields) q-        return (fields, rows)---- | Ask MySQL to prepare a query statement.----prepareStmt :: MySQLConn -> Query -> IO StmtID-prepareStmt conn@(MySQLConn is os _ _) (Query stmt) = do-    guardUnconsumed conn-    writeCommand (COM_STMT_PREPARE stmt) os-    p <- readPacket is-    if isERR p-    then decodeFromPacket p >>= throwIO . ERRException-    else do-        StmtPrepareOK stid colCnt paramCnt _ <- getFromPacket getStmtPrepareOK p-        _ <- replicateM_ paramCnt (readPacket is)-        _ <- unless (paramCnt == 0) (void (readPacket is))  -- EOF-        _ <- replicateM_ colCnt (readPacket is)-        _ <- unless (colCnt == 0) (void (readPacket is))  -- EOF-        return stid---- | Ask MySQL to prepare a query statement.------ All details from @COM_STMT_PREPARE@ Response are returned: the 'StmtPrepareOK' packet,--- params's 'ColumnDef', result's 'ColumnDef'.----prepareStmtDetail :: MySQLConn -> Query -> IO (StmtPrepareOK, [ColumnDef], [ColumnDef])-prepareStmtDetail conn@(MySQLConn is os _ _) (Query stmt) = do-    guardUnconsumed conn-    writeCommand (COM_STMT_PREPARE stmt) os-    p <- readPacket is-    if isERR p-    then decodeFromPacket p >>= throwIO . ERRException-    else do-        sOK@(StmtPrepareOK _ colCnt paramCnt _) <- getFromPacket getStmtPrepareOK p-        pdefs <- replicateM paramCnt ((decodeFromPacket <=< readPacket) is)-        _ <- unless (paramCnt == 0) (void (readPacket is))  -- EOF-        cdefs <- replicateM colCnt ((decodeFromPacket <=< readPacket) is)-        _ <- unless (colCnt == 0) (void (readPacket is))  -- EOF-        return (sOK, pdefs, cdefs)---- | Ask MySQL to closed a query statement.----closeStmt :: MySQLConn -> StmtID -> IO ()-closeStmt (MySQLConn _ os _ _) stid = do-    writeCommand (COM_STMT_CLOSE stid) os---- | Ask MySQL to reset a query statement, all previous resultset will be cleared.----resetStmt :: MySQLConn -> StmtID -> IO ()-resetStmt (MySQLConn is os _ consumed) stid = do-    writeCommand (COM_STMT_RESET stid) os  -- previous result-set may still be unconsumed-    p <- readPacket is-    if isERR p-    then decodeFromPacket p >>= throwIO . ERRException-    else writeIORef consumed True---- | Execute prepared query statement with parameters, expecting no resultset.----executeStmt :: MySQLConn -> StmtID -> [MySQLValue] -> IO OK-executeStmt conn stid params =-  command conn (COM_STMT_EXECUTE stid params (makeNullMap params))---- | Execute prepared query statement with parameters, expecting resultset.------ Rules about 'UnconsumedResultSet' applied here too.----queryStmt :: MySQLConn -> StmtID -> [MySQLValue] -> IO ([ColumnDef], InputStream [MySQLValue])-queryStmt conn@(MySQLConn is os _ consumed) stid params = do-    guardUnconsumed conn-    writeCommand (COM_STMT_EXECUTE stid params (makeNullMap params)) os-    p <- readPacket is-    if isERR p-    then decodeFromPacket p >>= throwIO . ERRException-    else do-        len <- getFromPacket getLenEncInt p-        fields <- replicateM len $ (decodeFromPacket <=< readPacket) is-        _ <- readPacket is -- eof packet, we don't verify this though-        writeIORef consumed False-        rows <- Stream.makeInputStream $ do-            q <- readPacket is-            if  | isOK  q  -> Just <$> getFromPacket (getBinaryRow fields len) q-                | isEOF q  -> writeIORef consumed True >> return Nothing-                | isERR q  -> decodeFromPacket q >>= throwIO . ERRException-                | otherwise -> throwIO (UnexpectedPacket q)-        return (fields, rows)---- | 'V.Vector' version of 'queryStmt'------ @since 0.5.1.0----queryStmtVector :: MySQLConn -> StmtID -> [MySQLValue] -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))-queryStmtVector conn@(MySQLConn is os _ consumed) stid params = do-    guardUnconsumed conn-    writeCommand (COM_STMT_EXECUTE stid params (makeNullMap params)) os-    p <- readPacket is-    if isERR p-    then decodeFromPacket p >>= throwIO . ERRException-    else do-        len <- getFromPacket getLenEncInt p-        fields <- V.replicateM len $ (decodeFromPacket <=< readPacket) is-        _ <- readPacket is -- eof packet, we don't verify this though-        writeIORef consumed False-        rows <- Stream.makeInputStream $ do-            q <- readPacket is-            if  | isOK  q  -> Just <$> getFromPacket (getBinaryRowVector fields len) q-                | isEOF q  -> writeIORef consumed True >> return Nothing-                | isERR q  -> decodeFromPacket q >>= throwIO . ERRException-                | otherwise -> throwIO (UnexpectedPacket q)-        return (fields, rows)---- | Run querys inside a transaction, querys will be rolled back if exception arise.------ @since 0.2.0.0----withTransaction :: MySQLConn -> IO a -> IO a-withTransaction conn procedure = mask $ \restore -> do-  _ <- execute_ conn "BEGIN"-  r <- restore procedure `onException` (execute_ conn "ROLLBACK")-  _ <- execute_ conn "COMMIT"-  pure r
− Database/MySQL/BinLog.hs
@@ -1,214 +0,0 @@-{-|-Module      : Database.MySQL.BinLog-Description : Binary protocol toolkit-Copyright   : (c) Winterland, 2016-License     : BSD-Maintainer  : drkoster@qq.com-Stability   : experimental-Portability : PORTABLE--This module provide tools for binlog listening and row based binlog decoding.--}--module Database.MySQL.BinLog-    ( -- * binlog utilities-      SlaveID-    , BinLogTracker(..)-    , registerPesudoSlave-    , dumpBinLog-    , RowBinLogEvent(..)-    , decodeRowBinLogEvent-    -- * helpers-    , getLastBinLogTracker-    , isCheckSumEnabled-    , isSemiSyncEnabled-    -- * re-export-    , module Database.MySQL.BinLogProtocol.BinLogEvent-    , module Database.MySQL.BinLogProtocol.BinLogValue-    , module Database.MySQL.BinLogProtocol.BinLogMeta-    ) where--import           Control.Applicative-import           Control.Exception                         (throwIO)-import           Control.Monad-import           Data.Binary.Put-import           Data.ByteString                           (ByteString)-import           Data.IORef                                (IORef, newIORef,-                                                            readIORef,-                                                            writeIORef)-import           Data.Text.Encoding                        (encodeUtf8)-import           Data.Word-import           Database.MySQL.Base-import           Database.MySQL.BinLogProtocol.BinLogEvent-import           Database.MySQL.BinLogProtocol.BinLogMeta-import           Database.MySQL.BinLogProtocol.BinLogValue-import           Database.MySQL.Connection-import           GHC.Generics                              (Generic)-import           System.IO.Streams                         (InputStream)-import qualified System.IO.Streams                         as Stream--type SlaveID = Word32---- | binlog filename and position to start listening.----data BinLogTracker = BinLogTracker-    { btFileName :: {-# UNPACK #-} !ByteString-    , btNextPos  :: {-# UNPACK #-} !Word32-    } deriving (Show, Eq, Generic)---- | Register a pesudo slave to master, although MySQL document suggests you should call this--- before calling 'dumpBinLog', but it seems it's not really necessary.----registerPesudoSlave :: MySQLConn -> SlaveID -> IO OK-registerPesudoSlave conn sid = command conn (COM_REGISTER_SLAVE sid "" "" "" 0 0 0)---- | Setup binlog listening on given connection, during listening--- the connection CAN NOT be used to do query, or an 'UnconsumedResultSet' will be thrown.----dumpBinLog :: MySQLConn               -- ^ connection to be listened-           -> SlaveID                 -- ^ a number for our pesudo slave.-           -> BinLogTracker           -- ^ binlog position-           -> Bool                    -- ^ if master support semi-ack, do we want to enable it?-                                      -- if master doesn't support, this parameter will be ignored.-           -> IO (FormatDescription, IORef ByteString, InputStream BinLogPacket)-                -- ^ 'FormatDescription', 'IORef' contains current binlog filename, 'BinLogPacket' stream.-dumpBinLog conn@(MySQLConn is wp _ consumed) sid (BinLogTracker initfn initpos) wantAck = do-    guardUnconsumed conn-    checksum <- isCheckSumEnabled conn-    when checksum $ void $ execute_ conn "SET @master_binlog_checksum = @@global.binlog_checksum"-    semiAck  <- isSemiSyncEnabled conn-    let needAck = semiAck && wantAck-    when needAck . void $ execute_ conn "SET @rpl_semi_sync_slave = 1"-    writeCommand (COM_BINLOG_DUMP initpos 0x00 sid initfn) wp-    writeIORef consumed False--    rp <- skipToPacketT (readBinLogPacket checksum needAck is) BINLOG_ROTATE_EVENT-    re <- getFromBinLogPacket getRotateEvent rp-    fref <- newIORef (rFileName re)--    p <- skipToPacketT (readBinLogPacket checksum needAck is) BINLOG_FORMAT_DESCRIPTION_EVENT-    replyAck needAck p fref wp-    fmt <- getFromBinLogPacket getFormatDescription p--    es <- Stream.makeInputStream $ do-        q <- readBinLogPacket checksum needAck is-        case q of-            Nothing   -> writeIORef consumed True >> return Nothing-            Just q' -> do when (blEventType q' == BINLOG_ROTATE_EVENT) $ do-                                e <- getFromBinLogPacket getRotateEvent q'-                                writeIORef' fref (rFileName e)-                          replyAck needAck q' fref wp-                          return q-    return (fmt, fref, es)-  where-    skipToPacketT iop typ = do-        p <- iop-        case p of-            Just p' -> do-                if blEventType p' == typ then return p' else skipToPacketT iop typ-            Nothing -> throwIO NetworkException--    replyAck needAck p fref wp' = when (needAck && blSemiAck p) $ do-        fn <- readIORef fref-        wp' (makeSemiAckPacket (blLogPos p) fn)--    makeSemiAckPacket pos fn = putToPacket 0 $ do-        putWord8 0xEF      -- semi-ack-        putWord64le pos-        putByteString fn--    readBinLogPacket checksum needAck is' = do-        p <- readPacket is'-        if  | isOK p -> Just <$> getFromPacket (getBinLogPacket checksum needAck) p-            | isEOF p -> return Nothing-            | isERR p -> decodeFromPacket p >>= throwIO . ERRException---- | Row based binlog event type.------ It's recommended to enable row query event before 'dumpBinLog', so that you can get--- 'RowQueryEvent' in row based binlog(it's important for detect a table change for example),--- more information please refer <http://dev.mysql.com/doc/refman/5.7/en/replication-options-binary-log.html#sysvar_binlog_rows_query_log_events sysvar_binlog_rows_query_log_events>------ A 'BinLogTracker' is included so that you can roll up your own HA solutions,--- for example, writing the tracker to zookeeper when you done with an event.------ The first 'Word32' field is a timestamp present when this event is logged.----data RowBinLogEvent-    = RowQueryEvent  {-# UNPACK #-} !Word32 !BinLogTracker !QueryEvent'-    | RowDeleteEvent {-# UNPACK #-} !Word32 !BinLogTracker !TableMapEvent !DeleteRowsEvent-    | RowWriteEvent  {-# UNPACK #-} !Word32 !BinLogTracker !TableMapEvent !WriteRowsEvent-    | RowUpdateEvent {-# UNPACK #-} !Word32 !BinLogTracker !TableMapEvent !UpdateRowsEvent-  deriving (Show, Eq, Generic)---- | decode row based event from 'BinLogPacket' stream.-decodeRowBinLogEvent :: (FormatDescription, IORef ByteString, InputStream BinLogPacket)-                     -> IO (InputStream RowBinLogEvent)-decodeRowBinLogEvent (fd', fref', is') = Stream.makeInputStream (loop fd' fref' is')-  where-    loop fd fref is = do-        p <- Stream.read is-        case p of-            Nothing -> return Nothing-            Just p' -> do-                let t = blEventType p'-                if  | t == BINLOG_ROWS_QUERY_EVENT -> do-                        tr <- track p' fref-                        e <- getFromBinLogPacket getQueryEvent' p'-                        pure (Just (RowQueryEvent (blTimestamp p') tr e))-                    | t == BINLOG_TABLE_MAP_EVENT -> do-                        tme <- getFromBinLogPacket (getTableMapEvent fd) p'-                        q <- Stream.read is-                        case q of-                            Nothing -> return Nothing-                            Just q' -> do-                                let u = blEventType q'-                                if  | u == BINLOG_WRITE_ROWS_EVENTv1 || u == BINLOG_WRITE_ROWS_EVENTv2 -> do-                                        tr <- track q' fref-                                        e <- getFromBinLogPacket' (getWriteRowEvent fd tme) q'-                                        pure (Just (RowWriteEvent (blTimestamp q') tr tme e))-                                    | u == BINLOG_DELETE_ROWS_EVENTv1 || u == BINLOG_DELETE_ROWS_EVENTv2 -> do-                                        tr <- track q' fref-                                        e <- getFromBinLogPacket' (getDeleteRowEvent fd tme) q'-                                        pure (Just (RowDeleteEvent (blTimestamp q') tr tme e))-                                    | u == BINLOG_UPDATE_ROWS_EVENTv1 || u == BINLOG_UPDATE_ROWS_EVENTv2 -> do-                                        tr <- track q' fref-                                        e <- getFromBinLogPacket' (getUpdateRowEvent fd tme) q'-                                        pure (Just (RowUpdateEvent (blTimestamp q') tr tme e))-                                    | otherwise -> loop fd fref is-                    | otherwise -> loop fd fref is--    track p fref = BinLogTracker <$> readIORef fref <*> (pure . fromIntegral . blLogPos) p---- | Get latest master's binlog filename and position.----getLastBinLogTracker :: MySQLConn -> IO (Maybe BinLogTracker)-getLastBinLogTracker conn = do-    (_, is) <- query_ conn "SHOW MASTER STATUS"-    row <- Stream.read is-    Stream.skipToEof is-    case row of-        Just (MySQLText fn : MySQLInt64U pos : _) -> return . Just $ BinLogTracker (encodeUtf8 fn) (fromIntegral pos)-        _                                         -> return Nothing---- | Return True if binlog_checksum = CRC32. Only for MySQL > 5.6----isCheckSumEnabled :: MySQLConn -> IO Bool-isCheckSumEnabled conn = do-    (_, is) <- query_ conn "SHOW GLOBAL VARIABLES LIKE 'binlog_checksum'"-    row <- Stream.read is-    Stream.skipToEof is-    case row of-        Just [_, MySQLText "CRC32"] -> return True-        _                           -> return False---- | Return True if rpl_semi_sync_master_enabled = ON. Only for MySQL > 5.5----isSemiSyncEnabled :: MySQLConn -> IO Bool-isSemiSyncEnabled conn = do-    (_, is) <- query_ conn "SHOW VARIABLES LIKE 'rpl_semi_sync_master_enabled'"-    row <- Stream.read is-    Stream.skipToEof is-    case row of-        Just [_, MySQLText "ON"] -> return True-        _                        -> return False
− Database/MySQL/BinLogProtocol/BinLogEvent.hs
@@ -1,291 +0,0 @@-{-# OPTIONS_GHC -funbox-strict-fields #-}--{-|-Module      : Database.MySQL.BinLogProtocol.BinLogEvent-Description : Binlog event-Copyright   : (c) Winterland, 2016-License     : BSD-Maintainer  : drkoster@qq.com-Stability   : experimental-Portability : PORTABLE--Binlog event type--}--module Database.MySQL.BinLogProtocol.BinLogEvent where--import           Control.Applicative-import           Control.Monad-import           Control.Monad.Loops                       (untilM)-import           Data.Binary-import           Data.Binary.Parser-import           Data.Bits-import           Data.ByteString                           (ByteString)-import qualified Data.ByteString                           as B-import qualified Data.ByteString.Lazy                      as L-import qualified Data.ByteString.Unsafe                    as B-import           Database.MySQL.BinLogProtocol.BinLogMeta-import           Database.MySQL.BinLogProtocol.BinLogValue-import           Database.MySQL.Protocol.Packet-import           Database.MySQL.Protocol.MySQLValue-import           Database.MySQL.Protocol.ColumnDef--import           Control.Exception                         (throwIO)-import           Database.MySQL.Query-import           GHC.Generics                              (Generic)------------------------------------------------------------------------------------- | binlog tyoe----data BinLogEventType-    = BINLOG_UNKNOWN_EVENT-    | BINLOG_START_EVENT_V3-    | BINLOG_QUERY_EVENT-    | BINLOG_STOP_EVENT-    | BINLOG_ROTATE_EVENT-    | BINLOG_INTVAR_EVENT-    | BINLOG_LOAD_EVENT-    | BINLOG_SLAVE_EVENT-    | BINLOG_CREATE_FILE_EVENT-    | BINLOG_APPEND_BLOCK_EVENT-    | BINLOG_EXEC_LOAD_EVENT-    | BINLOG_DELETE_FILE_EVENT-    | BINLOG_NEW_LOAD_EVENT-    | BINLOG_RAND_EVENT-    | BINLOG_USER_VAR_EVENT-    | BINLOG_FORMAT_DESCRIPTION_EVENT-    | BINLOG_XID_EVENT-    | BINLOG_BEGIN_LOAD_QUERY_EVENT-    | BINLOG_EXECUTE_LOAD_QUERY_EVENT-    | BINLOG_TABLE_MAP_EVENT-    | BINLOG_WRITE_ROWS_EVENTv0-    | BINLOG_UPDATE_ROWS_EVENTv0-    | BINLOG_DELETE_ROWS_EVENTv0-    | BINLOG_WRITE_ROWS_EVENTv1-    | BINLOG_UPDATE_ROWS_EVENTv1-    | BINLOG_DELETE_ROWS_EVENTv1-    | BINLOG_INCIDENT_EVENT-    | BINLOG_HEARTBEAT_EVENT-    | BINLOG_IGNORABLE_EVENT-    | BINLOG_ROWS_QUERY_EVENT-    | BINLOG_WRITE_ROWS_EVENTv2-    | BINLOG_UPDATE_ROWS_EVENTv2-    | BINLOG_DELETE_ROWS_EVENTv2-    | BINLOG_GTID_EVENT-    | BINLOG_ANONYMOUS_GTID_EVENT-    | BINLOG_PREVIOUS_GTIDS_EVENT-  deriving (Show, Eq, Enum)--data BinLogPacket = BinLogPacket-    { blTimestamp :: !Word32-    , blEventType :: !BinLogEventType-    , blServerId  :: !Word32-    , blEventSize :: !Word32-    , blLogPos    :: !Word64   -- ^ for future GTID compatibility-    , blFlags     :: !Word16-    , blBody      :: !L.ByteString-    , blSemiAck   :: !Bool-    } deriving (Show, Eq)--putSemiAckResp :: Word32 -> ByteString -> Put-putSemiAckResp pos fn = put pos >> put fn--getBinLogPacket :: Bool -> Bool -> Get BinLogPacket-getBinLogPacket checksum semi = do-    _  <- getWord8     -- OK byte-    ack <- if semi-        then getWord8 >> (== 0x01) <$> getWord8-        else return False-    ts <- getWord32le-    typ <- toEnum . fromIntegral <$> getWord8-    sid <- getWord32le-    size <- getWord32le-    pos <- getWord32le-    flgs <- getWord16le-    body <- getLazyByteString (fromIntegral size - if checksum then 23 else 19)-    return (BinLogPacket ts typ sid size (fromIntegral pos) flgs body ack)--getFromBinLogPacket :: Get a -> BinLogPacket -> IO a-getFromBinLogPacket g (BinLogPacket _ _ _ _ _ _ body _ ) =-    case parseDetailLazy g body  of-        Left  (buf, offset, errmsg) -> throwIO (DecodePacketFailed buf offset errmsg)-        Right (_,   _,      r     ) -> return r--getFromBinLogPacket' :: (BinLogEventType -> Get a) -> BinLogPacket -> IO a-getFromBinLogPacket' g (BinLogPacket _ typ _ _ _ _ body _ ) =-    case parseDetailLazy (g typ) body  of-        Left  (buf, offset, errmsg) -> throwIO (DecodePacketFailed buf offset errmsg)-        Right (_,   _,      r     ) -> return r------------------------------------------------------------------------------------data FormatDescription = FormatDescription-    { fdVersion              :: !Word16-    , fdMySQLVersion         :: !ByteString-    , fdCreateTime           :: !Word32-    -- , eventHeaderLen :: !Word8  -- const 19-    , fdEventHeaderLenVector :: !ByteString  -- ^ a array indexed by Binlog Event Type - 1-                                             -- to extract the length of the event specific header.-    } deriving (Show, Eq, Generic)--getFormatDescription :: Get FormatDescription-getFormatDescription = FormatDescription <$> getWord16le-                                         <*> getByteString 50-                                         <*> getWord32le-                                         <*  getWord8-                                         <*> (L.toStrict <$> getRemainingLazyByteString)--eventHeaderLen :: FormatDescription -> BinLogEventType -> Word8-eventHeaderLen fd typ = B.unsafeIndex (fdEventHeaderLenVector fd) (fromEnum typ - 1)--data RotateEvent = RotateEvent-    { rPos :: !Word64, rFileName :: !ByteString } deriving (Show, Eq)--getRotateEvent :: Get RotateEvent-getRotateEvent = RotateEvent <$> getWord64le <*> getRemainingByteString---- | This's query parser for statement based binlog's query event, it's actually--- not used in row based binlog.----data QueryEvent = QueryEvent-    { qSlaveProxyId :: !Word32-    , qExecTime     :: !Word32-    , qErrCode      :: !Word16-    , qStatusVars   :: !ByteString-    , qSchemaName   :: !ByteString-    , qQuery        :: !Query-    } deriving (Show, Eq, Generic)--getQueryEvent :: Get QueryEvent-getQueryEvent = do-    pid <- getWord32le-    tim <- getWord32le-    slen <- getWord8-    ecode <- getWord16le-    vlen <- getWord16le-    svar <- getByteString (fromIntegral vlen)-    schema <- getByteString (fromIntegral slen)-    _ <- getWord8-    qry <- getRemainingLazyByteString-    return (QueryEvent pid tim ecode svar schema (Query qry))---- | This's the query event in row based binlog.----data QueryEvent' = QueryEvent' { qQuery' :: !Query } deriving (Show, Eq)--getQueryEvent' :: Get QueryEvent'-getQueryEvent' = do-    _ <- getWord8-    QueryEvent' . Query <$> getRemainingLazyByteString--data TableMapEvent = TableMapEvent-    { tmTableId    :: !Word64-    , tmFlags      :: !Word16-    , tmSchemaName :: !ByteString-    , tmTableName  :: !ByteString-    , tmColumnCnt  :: !Int-    , tmColumnType :: ![FieldType]-    , tmColumnMeta :: ![BinLogMeta]-    , tmNullMap    :: !ByteString-    } deriving (Show, Eq, Generic)--getTableMapEvent :: FormatDescription -> Get TableMapEvent-getTableMapEvent fd = do-    let hlen = eventHeaderLen fd BINLOG_TABLE_MAP_EVENT-    tid <- if hlen == 6 then fromIntegral <$> getWord32le else getWord48le-    flgs <- getWord16le-    slen <- getWord8-    schema <- getByteString (fromIntegral slen)-    _ <- getWord8 -- 0x00-    tlen <- getWord8-    table <- getByteString (fromIntegral tlen)-    _ <- getWord8 -- 0x00-    cc <- getLenEncInt-    colTypBS <- getByteString cc-    let typs = map FieldType (B.unpack colTypBS)-    colMetaBS <- getLenEncBytes--    metas <- case runGetOrFail (forM typs getBinLogMeta) (L.fromStrict colMetaBS) of-        Left (_, _, errmsg) -> fail errmsg-        Right (_, _, r)     -> return r--    nullmap <- getByteString ((cc + 7) `div` 8)-    return (TableMapEvent tid flgs schema table cc typs metas nullmap)--data DeleteRowsEvent = DeleteRowsEvent-    { deleteTableId    :: !Word64-    , deleteFlags      :: !Word16-    -- , deleteExtraData   :: !RowsEventExtraData-    , deleteColumnCnt  :: !Int-    , deletePresentMap :: !BitMap-    , deleteRowData    :: ![[BinLogValue]]-    } deriving (Show, Eq, Generic)--getDeleteRowEvent :: FormatDescription -> TableMapEvent -> BinLogEventType -> Get DeleteRowsEvent-getDeleteRowEvent fd tme typ = do-    let hlen = eventHeaderLen fd typ-    tid <- if hlen == 6 then fromIntegral <$> getWord32le else getWord48le-    flgs <- getWord16le-    when (typ == BINLOG_DELETE_ROWS_EVENTv2) $ do-        extraLen <- getWord16le-        void $ getByteString (fromIntegral extraLen - 2)-    colCnt <- getLenEncInt-    let (plen, poffset) = (fromIntegral colCnt + 7) `quotRem` 8-    pmap <- getPresentMap plen poffset-    DeleteRowsEvent tid flgs colCnt pmap <$> untilM (getBinLogRow (tmColumnMeta tme) pmap) isEmpty--data WriteRowsEvent = WriteRowsEvent-    { writeTableId    :: !Word64-    , writeFlags      :: !Word16-    -- , writeExtraData   :: !RowsEventExtraData-    , writeColumnCnt  :: !Int-    , writePresentMap :: !BitMap-    , writeRowData    :: ![[BinLogValue]]-    } deriving (Show, Eq, Generic)--getWriteRowEvent :: FormatDescription -> TableMapEvent -> BinLogEventType -> Get WriteRowsEvent-getWriteRowEvent fd tme typ = do-    let hlen = eventHeaderLen fd typ-    tid <- if hlen == 6 then fromIntegral <$> getWord32le else getWord48le-    flgs <- getWord16le-    when (typ == BINLOG_WRITE_ROWS_EVENTv2) $ do-        extraLen <- getWord16le-        void $ getByteString (fromIntegral extraLen - 2)-    colCnt <- getLenEncInt-    let (plen, poffset) = (fromIntegral colCnt + 7) `quotRem` 8-    pmap <- getPresentMap plen poffset-    WriteRowsEvent tid flgs colCnt pmap <$> untilM (getBinLogRow (tmColumnMeta tme) pmap) isEmpty--data UpdateRowsEvent = UpdateRowsEvent-    { updateTableId    :: !Word64-    , updateFlags      :: !Word16-    -- , updateExtraData   :: !RowsEventExtraData-    , updateColumnCnt  :: !Int-    , updatePresentMap :: !(BitMap, BitMap)-    , updateRowData    :: ![ ([BinLogValue], [BinLogValue]) ]-    } deriving (Show, Eq, Generic)--getUpdateRowEvent :: FormatDescription -> TableMapEvent -> BinLogEventType -> Get UpdateRowsEvent-getUpdateRowEvent fd tme typ = do-    let hlen = eventHeaderLen fd typ-    tid <- if hlen == 6 then fromIntegral <$> getWord32le else getWord48le-    flgs <- getWord16le-    when (typ == BINLOG_UPDATE_ROWS_EVENTv2) $ do-        extraLen <- getWord16le-        void $ getByteString (fromIntegral extraLen - 2)-    colCnt <- getLenEncInt-    let (plen, poffset) = (fromIntegral colCnt + 7) `quotRem` 8-    pmap <- getPresentMap plen poffset-    pmap' <- getPresentMap plen poffset-    UpdateRowsEvent tid flgs colCnt (pmap, pmap') <$>-        untilM ((,) <$> getBinLogRow (tmColumnMeta tme) pmap <*> getBinLogRow (tmColumnMeta tme) pmap')-               isEmpty--getPresentMap :: Int -> Int -> Get BitMap-getPresentMap plen poffset = do-    pmap <- getByteString plen-    let pmap' = if B.null pmap-                then B.empty-                else B.init pmap `B.snoc` (B.last pmap .&. 0xFF `shiftR` (7 - poffset))-    pure (BitMap pmap')-
− Database/MySQL/BinLogProtocol/BinLogMeta.hs
@@ -1,120 +0,0 @@-{-# OPTIONS_GHC -funbox-strict-fields #-}--{-|-Module      : Database.MySQL.BinLogProtocol.BinLogMeta-Description : Binlog protocol column meta-Copyright   : (c) Winterland, 2016-License     : BSD-Maintainer  : drkoster@qq.com-Stability   : experimental-Portability : PORTABLE--This module provide column meta decoder for binlog protocol.--There're certain type won't appear in binlog event, and some types are compressed into 'mySQLTypeString'-, please take python version as a reference:  <https://github.com/noplay/python-mysql-replication>--You will not directly meet following 'FieldType' namely:--    * mySQLTypeDecimal-    * mySQLTypeNewdate-    * mySQLTypeEnum-    * mySQLTypeSet-    * mySQLTypeTinyBlob-    * mySQLTypeMediumBlOb-    * mySQLTypeLongBlob---}--module Database.MySQL.BinLogProtocol.BinLogMeta where--import           Control.Applicative-import           Data.Binary.Get-import           Data.Bits-import           Data.Word-import           Database.MySQL.Protocol.ColumnDef---- | An intermedia date type for decoding row-based event's values.----data BinLogMeta-    = BINLOG_TYPE_TINY-    | BINLOG_TYPE_SHORT-    | BINLOG_TYPE_INT24-    | BINLOG_TYPE_LONG-    | BINLOG_TYPE_LONGLONG-    | BINLOG_TYPE_FLOAT       !Word8         -- ^ size-    | BINLOG_TYPE_DOUBLE      !Word8         -- ^ size-    | BINLOG_TYPE_BIT         !Word16 !Word8 -- ^ bits, bytes-    | BINLOG_TYPE_TIMESTAMP-    | BINLOG_TYPE_DATETIME-    | BINLOG_TYPE_DATE-    | BINLOG_TYPE_TIME-    | BINLOG_TYPE_TIMESTAMP2  !Word8         -- ^ fsp-    | BINLOG_TYPE_DATETIME2   !Word8         -- ^ fsp-    | BINLOG_TYPE_TIME2       !Word8         -- ^ fsp-    | BINLOG_TYPE_YEAR-    | BINLOG_TYPE_NEWDECIMAL  !Word8 !Word8  -- ^ precision, scale-    | BINLOG_TYPE_ENUM        !Word8         -- ^ 1 or 2('Word8' or 'Word16'), enum index size-    | BINLOG_TYPE_SET         !Word16 !Word8 -- ^ bitmap bits, bytes-    | BINLOG_TYPE_BLOB        !Word8         -- ^ length size-    | BINLOG_TYPE_STRING      !Word16        -- ^ meta length(if < 256, then length is 8bit,-                                             -- if > 256 then length is 16bit)-    | BINLOG_TYPE_GEOMETRY    !Word8         -- ^ length size-  deriving (Show, Eq)--getBinLogMeta :: FieldType -> Get BinLogMeta-getBinLogMeta t-    | t == mySQLTypeTiny       = pure BINLOG_TYPE_TINY-    | t == mySQLTypeShort      = pure BINLOG_TYPE_SHORT-    | t == mySQLTypeInt24      = pure BINLOG_TYPE_INT24-    | t == mySQLTypeLong       = pure BINLOG_TYPE_LONG-    | t == mySQLTypeLongLong   = pure BINLOG_TYPE_LONGLONG-    | t == mySQLTypeFloat      = BINLOG_TYPE_FLOAT <$> getWord8-    | t == mySQLTypeDouble     = BINLOG_TYPE_DOUBLE <$> getWord8--    | t == mySQLTypeBit        = do-        byte0 <- getWord8-        byte1 <- getWord8-        let nbits = (fromIntegral byte1 `shiftL` 3) .|.  fromIntegral byte0-            nbytes = fromIntegral $ (nbits + 7) `shiftR` 3-        pure (BINLOG_TYPE_BIT nbits nbytes)--    | t == mySQLTypeTimestamp  = pure BINLOG_TYPE_TIMESTAMP-    | t == mySQLTypeDateTime   = pure BINLOG_TYPE_DATETIME-    | t == mySQLTypeDate       = pure BINLOG_TYPE_DATE-    | t == mySQLTypeTime       = pure BINLOG_TYPE_TIME-    | t == mySQLTypeTimestamp2 = BINLOG_TYPE_TIMESTAMP2 <$> getWord8-    | t == mySQLTypeDateTime2  = BINLOG_TYPE_DATETIME2 <$> getWord8-    | t == mySQLTypeTime2      = BINLOG_TYPE_TIME2 <$> getWord8-    | t == mySQLTypeYear       = pure BINLOG_TYPE_YEAR-    | t == mySQLTypeNewDecimal = BINLOG_TYPE_NEWDECIMAL <$> getWord8 <*> getWord8-    | t == mySQLTypeVarChar    = BINLOG_TYPE_STRING <$> getWord16le-    | t == mySQLTypeVarString  = BINLOG_TYPE_STRING <$> getWord16le--    | t == mySQLTypeString     = do-        byte0 <- getWord8-        byte1 <- getWord8-        -- http://bugs.mysql.com/37426-        if  byte0 > 0-        then if (byte0 .&. 0x30) /= 0x30-             then if FieldType (byte0 .|. 0x30) == mySQLTypeString-                  then let len = fromIntegral $ (byte0 .&. 0x30) `xor` 0x30-                           len' = len `shiftL` 4 .|. fromIntegral byte1-                       in pure $! BINLOG_TYPE_STRING len'-                  else let len = fromIntegral byte0 `shiftL` 8 :: Word16-                           len' = len .|. fromIntegral byte1-                       in pure $! BINLOG_TYPE_STRING len'-             else let t' = FieldType byte0-                  in if | t' == mySQLTypeSet    -> let nbits = fromIntegral byte1 `shiftL` 3-                                                       nbytes = fromIntegral $ (nbits + 7) `shiftR` 8-                                                   in pure (BINLOG_TYPE_SET nbits nbytes)-                        | t' == mySQLTypeEnum   -> pure (BINLOG_TYPE_ENUM byte1)-                        | t' == mySQLTypeString -> pure (BINLOG_TYPE_STRING (fromIntegral byte1))-                        | otherwise             -> fail $ "Database.MySQL.BinLogProtocol.BinLogMeta:\-                                                           \ impossible type inside binlog string: " ++ show t'-        else pure (BINLOG_TYPE_STRING (fromIntegral byte1))--    | t == mySQLTypeBlob       = BINLOG_TYPE_BLOB <$> getWord8-    | t == mySQLTypeGeometry   = BINLOG_TYPE_GEOMETRY <$> getWord8-    | otherwise                = fail $ "Database.MySQL.BinLogProtocol.BinLogMeta:\-                                        \ impossible type in binlog: " ++ show t
− Database/MySQL/BinLogProtocol/BinLogValue.hs
@@ -1,325 +0,0 @@-{-|-Module      : Database.MySQL.BinLogProtocol.BinLogValue-Description : Binlog protocol-Copyright   : (c) Winterland, 2016-License     : BSD-Maintainer  : drkoster@qq.com-Stability   : experimental-Portability : PORTABLE--Binlog protocol---}--module Database.MySQL.BinLogProtocol.BinLogValue where--import           Control.Applicative-import           Data.Binary.Get-import           Data.Binary.IEEE754-import           Data.Binary.Put                          ()-import           Data.Bits-import           Data.ByteString                          (ByteString)-import qualified Data.ByteString                          as B-import qualified Data.ByteString.Unsafe                   as B-import           Data.Int-import           Data.Int.Int24-import           Data.Scientific-import           Data.Word-import           Database.MySQL.BinLogProtocol.BinLogMeta-import           Database.MySQL.Protocol.MySQLValue-import           Database.MySQL.Protocol.Packet-import           GHC.Generics                             (Generic)---- | Data type for representing binlog values.------ This data type DOES NOT try to parse binlog values into detailed haskell values,--- because you may not want to waste performance in situations like database middleware.------ Due to the lack of signedness infomation in binlog meta, we cannot distinguish,--- for example, between unsigned tiny 255 and tiny -1, so we use int to present--- @TINY,SHORT,INT,LONG@. If you have unsigned columns, use 'fromIntegral' to convert it--- to word to get real unsigned value back, for example, @fromIntegral (-1 :: Int) == 255 :: Word@------ For above reason, we use 'Int24' to present MySQL's @INT24@ type, you can get back the--- unsigned value using @word24@ package's 'Word24' type.------ Timestamp types('BinLogTimeStamp' and 'BinLogTimeStamp2') are values converted into UTC already,--- see 'MySQLVaule' 's note.------ There's also no infomation about charset, so we use 'ByteString' to present both text--- and blob types, if you want to get text representation back, you have to query column charset--- infomation, and use icu or iconv to decode. IT MAY NOT BE UTF-8.------ The @SET@ and @ENUM@ values are presented by their index's value and bitmap respectively,--- if you need get the string value back, you have to perform a 'DESC tablename' to get the--- set or enum table.----data BinLogValue-    = BinLogTiny       !Int8-    | BinLogShort      !Int16-    | BinLogInt24      !Int24-    | BinLogLong       !Int32-    | BinLogLongLong   !Int64-    | BinLogFloat      !Float-    | BinLogDouble     !Double-    | BinLogBit        !Word64          -- ^ a 64bit bitmap.-    | BinLogTimeStamp  !Word32          -- ^ a utc timestamp, note 0 doesn't mean @1970-01-01 00:00:00@,-                                        -- because mysql choose 0 to present '0000-00-00 00:00:00'-    | BinLogTimeStamp2 !Word32 !Word32  -- ^ like 'BinLogTimeStamp' with an addtional microseconds field.-    | BinLogDateTime   !Word16 !Word8 !Word8 !Word8 !Word8 !Word8         -- ^ YYYY MM DD hh mm ss-    | BinLogDateTime2  !Word16 !Word8 !Word8 !Word8 !Word8 !Word8 !Word32 -- ^ YYYY MM DD hh mm ss microsecond-    | BinLogDate       !Word16 !Word8 !Word8                   -- ^ YYYY MM DD-    | BinLogTime       !Word8  !Word16 !Word8 !Word8           -- ^ sign(1= non-negative, 0= negative) hh mm ss-    | BinLogTime2      !Word8  !Word16 !Word8 !Word8 !Word32   -- ^ sign(1= non-negative, 0= negative) hh mm ss microsecond-    | BinLogYear       !Word16                                 -- ^ year value, 0 stand for '0000'-    | BinLogNewDecimal !Scientific                             -- ^ sign(1= non-negative, 0= negative) integeral part, fractional part-    | BinLogEnum       !Word16                                 -- ^ enum indexing value-    | BinLogSet        !Word64                                 -- ^ set indexing 64bit bitmap.-    | BinLogBytes      !ByteString                             -- ^ all string and blob values.-    | BinLogGeometry   !ByteString-    | BinLogNull-  deriving (Show, Eq, Generic)------------------------------------------------------------------------------------- | BinLog protocol decoder----getBinLogField :: BinLogMeta -> Get BinLogValue-getBinLogField BINLOG_TYPE_TINY                = BinLogTiny     <$> getInt8-getBinLogField BINLOG_TYPE_SHORT               = BinLogShort    <$> getInt16le-getBinLogField BINLOG_TYPE_INT24               = BinLogInt24 . fromIntegral <$> getWord24le-getBinLogField BINLOG_TYPE_LONG                = BinLogLong     <$> getInt32le-getBinLogField BINLOG_TYPE_LONGLONG            = BinLogLongLong <$> getInt64le-getBinLogField (BINLOG_TYPE_FLOAT  _         ) = BinLogFloat <$> getFloatle-getBinLogField (BINLOG_TYPE_DOUBLE _         ) = BinLogDouble <$> getDoublele-getBinLogField (BINLOG_TYPE_BIT    _    bytes) = BinLogBit <$> getBits' bytes-getBinLogField BINLOG_TYPE_TIMESTAMP           = BinLogTimeStamp <$> getWord32le---- A integer in @YYYYMMDD@ format, for example:--- 99991231 stand for @9999-12-31@-getBinLogField BINLOG_TYPE_DATE = do-    i <- getWord24le-    let (i', dd) = i `quotRem` 32-        (yyyy, mm) = i' `quotRem` 16-    pure (BinLogDate (fromIntegral yyyy)-                     (fromIntegral mm)-                     (fromIntegral dd))--getBinLogField (BINLOG_TYPE_TIMESTAMP2  fsp) = do-    s <- getWord32be -- big-endian here!-    ms <- fromIntegral <$> getMicroSecond fsp-    pure (BinLogTimeStamp2 s ms)---- A integer in @YYYYMMDDhhmmss@, for example:--- 99991231235959 stand for @9999-12-31 23:59:59@-getBinLogField BINLOG_TYPE_DATETIME = do-    i <- getWord64le-    let (yyyy, i')   = i      `quotRem` 10000000000-        (mm, i'')    = i'     `quotRem` 100000000-        (dd, i''')   = i''    `quotRem` 1000000-        (h, i'''')   = i'''   `quotRem` 10000-        (m, s)       = i''''  `quotRem` 100-    pure (BinLogDateTime (fromIntegral yyyy)-                         (fromIntegral mm)-                         (fromIntegral dd)-                         (fromIntegral h)-                         (fromIntegral m)-                         (fromIntegral s))---- BINLOG_TYPE_DATETIME2(big endian)------ 1 bit sign (used when on disk)--- 17 bits year * 13 + month (year 0-9999, month 0-12)--- 5 bits day (0-31)--- 5 bits hour (0-23)--- 6 bits minute (0-59)--- 6 bits second (0-59)--- (5 bytes in total)------ fractional-seconds storage (size depends on meta)----getBinLogField (BINLOG_TYPE_DATETIME2 fsp) = do-    iPart <- getWord40be-    let yyyymm = iPart `shiftR` 22 .&. 0x01FFFF -- 0b011111111111111111-        (yyyy, mm) = yyyymm `quotRem` 13-        yyyy' = fromIntegral yyyy-        mm' = fromIntegral mm-        dd = fromIntegral $ iPart `shiftR` 17 .&. 0x1F -- 0b00011111-        h =  fromIntegral $ iPart `shiftR` 12 .&. 0x1F -- 0b00011111-        m =  fromIntegral $ iPart `shiftR` 6 .&. 0x3F  -- 0b00111111-        s =  fromIntegral $ iPart .&. 0x3F             -- 0b00111111-    ms <- fromIntegral <$> getMicroSecond fsp-    pure (BinLogDateTime2 yyyy' mm' dd h m s ms)---- A integer in @hhmmss@ format(can be negative), for example:--- 8385959 stand for @838:59:59@-getBinLogField BINLOG_TYPE_TIME = do-    i <- getWord24le-    let i' =  fromIntegral i :: Int24-        sign = if i' >= 0 then 1 else 0-    let (h, i'')     = i'     `quotRem` 10000-        (m, s)       = i''    `quotRem` 100-    pure (BinLogTime sign (fromIntegral (abs h))-                          (fromIntegral (abs m))-                          (fromIntegral (abs s)))---- BINLOG_TYPE_TIME2(big endian)------ 1 bit sign  (1= non-negative, 0= negative)--- 1 bit unused (Reserved for wider hour range, e.g. for intervals)--- 10 bit hour (0-836)--- 6 bit minute (0-59)--- 6 bit second (0-59)--- (3 bytes in total)------ fractional-seconds storage (size depends on meta)----getBinLogField (BINLOG_TYPE_TIME2 fsp) = do-    iPart <- getWord24be-    let sign = fromIntegral $ iPart `shiftR` 23-        iPart' = if sign == 0 then 0x800000 - iPart - 1 else iPart-        h = fromIntegral (iPart' `shiftR` 12) .&. 0x03FF -- 0b0000001111111111-        m = fromIntegral (iPart' `shiftR` 6) .&. 0x3F    -- 0b00111111-        s = fromIntegral iPart' .&. 0x3F               -- 0b00111111-    ms <- abs <$> getMicroSecond fsp-    let ms' = abs (fromIntegral ms :: Int)-    pure (BinLogTime2 sign h m s (fromIntegral ms'))--getBinLogField BINLOG_TYPE_YEAR                = do-    y <- getWord8-    pure $! if y == 0 then BinLogYear 0 else BinLogYear (1900 + fromIntegral y)---- Decimal representation in binlog seems to be as follows:------ 1st bit - sign such that set == +, unset == ---- every 4 bytes represent 9 digits in big-endian order.------ 80 00 00 05 1b 38 b0 60 00 means:------   0x80 - positive---   0x00000005 - 5---   0x1b38b060 - 456700000---   0x00       - 0------ 54567000000 / 10^{10} = 5.4567------ if there're < 9 digits at first, it will be compressed into suitable length words--- following a simple lookup table.----getBinLogField (BINLOG_TYPE_NEWDECIMAL precision scale) = do-    let i = fromIntegral (precision - scale)-        (ucI, cI) = i `quotRem` digitsPerInteger-        (ucF, cF) = scale `quotRem` digitsPerInteger-        ucISize = fromIntegral (ucI `shiftL` 2)-        ucFSize = fromIntegral (ucF `shiftL` 2)-        cISize = fromIntegral (sizeTable `B.unsafeIndex` fromIntegral cI)-        cFSize = fromIntegral (sizeTable `B.unsafeIndex` fromIntegral cF)-        len = ucISize + cISize + ucFSize + cFSize--    buf <- getByteString (fromIntegral len)--    let fb = buf `B.unsafeIndex` 0-        sign = if fb .&. 0x80 == 0x80 then 1 else 0 :: Word8-        buf' = (fb `xor` 0x80) `B.cons` B.tail buf-        buf'' = if sign == 1 then buf'-                            else B.map (xor 0xFF) buf'--        iPart = fromIntegral (getCompressed cISize (B.unsafeTake cISize buf'')) * (blockSize ^ ucI)-              + getUncompressed ucI (B.unsafeDrop cISize buf'')--    let buf''' = B.unsafeDrop (ucISize + cISize) buf''--        fPart = getUncompressed ucF (B.unsafeTake ucFSize buf''') * (10 ^ cF)-              + fromIntegral (getCompressed cFSize (B.unsafeDrop ucFSize buf'''))--    let sci = scientific (iPart * 10 ^ scale + fPart) (negate $ fromIntegral scale)-        sci' = if sign == 0 then negate sci else sci-    pure (BinLogNewDecimal sci')-  where-    digitsPerInteger = 9-    blockSize = fromIntegral $ (10 :: Int32) ^ (9 :: Int)-    sizeTable = B.pack [0, 1, 1, 2, 2, 3, 3, 4, 4, 4]--    getCompressed :: Int -> ByteString -> Word64-    getCompressed 0 _  = 0-    getCompressed x bs = let fb = bs `B.unsafeIndex` 0-                             x' = x - 1-                         in fromIntegral fb `shiftL` (8 * x') .|. getCompressed x' (B.unsafeDrop 1 bs)--    getUncompressed :: Word8 -> ByteString -> Integer-    getUncompressed 0 _ = 0-    getUncompressed x bs = let v = getCompressed 4 (B.unsafeTake 4 bs)-                               x' = x - 1-                           in fromIntegral v * (blockSize ^ x') + getUncompressed x' (B.unsafeDrop 4 bs)---getBinLogField (BINLOG_TYPE_ENUM size) =-    if  | size == 1 -> BinLogEnum . fromIntegral <$> getWord8-        | size == 2 -> BinLogEnum . fromIntegral <$> getWord16be-        | otherwise -> fail $ "Database.MySQL.BinLogProtocol.BinLogValue: wrong \-                              \BINLOG_TYPE_ENUM size: " ++ show size---getBinLogField (BINLOG_TYPE_SET _ bytes) = BinLogSet <$> getBits' bytes-getBinLogField (BINLOG_TYPE_BLOB lensize) = do-    len <- if  | lensize == 1 -> fromIntegral <$> getWord8-               | lensize == 2 -> fromIntegral <$> getWord16le-               | lensize == 3 -> fromIntegral <$> getWord24le-               | lensize == 4 -> fromIntegral <$> getWord32le-               | otherwise    -> fail $ "Database.MySQL.BinLogProtocol.BinLogValue: \-                                        \wrong BINLOG_TYPE_BLOB length size: " ++ show lensize-    BinLogBytes <$> getByteString len--getBinLogField (BINLOG_TYPE_STRING size) = do-    len <- if | size < 256 -> fromIntegral <$> getWord8-              | otherwise  -> fromIntegral <$> getWord16le-    BinLogBytes <$> getByteString len--getBinLogField (BINLOG_TYPE_GEOMETRY lensize) = do-    len <- if | lensize == 1 -> fromIntegral <$> getWord8-              | lensize == 2 -> fromIntegral <$> getWord16le-              | lensize == 3 -> fromIntegral <$> getWord24le-              | lensize == 4 -> fromIntegral <$> getWord32le-              | otherwise    -> fail $  "Database.MySQL.BinLogProtocol.BinLogValue: \-                                        \wrong BINLOG_TYPE_GEOMETRY length size: " ++ show lensize-    BinLogGeometry <$> getByteString len--getMicroSecond :: Word8 -> Get Int32-getMicroSecond 0 = pure 0-getMicroSecond 1 = (* 100000) . fromIntegral <$> getInt8-getMicroSecond 2 = (* 10000) . fromIntegral <$> getInt8-getMicroSecond 3 = (* 1000) . fromIntegral <$> getInt16be-getMicroSecond 4 = (* 100) . fromIntegral <$> getInt16be-getMicroSecond 5 = (* 10) . fromIntegral <$> getInt24be-getMicroSecond 6 = fromIntegral <$> getInt24be-getMicroSecond _ = pure 0--getBits' :: Word8 -> Get Word64-getBits' bytes = if bytes <= 8-    then getBits (fromIntegral bytes)-    else fail $  "Database.MySQL.BinLogProtocol.BinLogValue: \-                 \wrong bit length size: " ++ show bytes------------------------------------------------------------------------------------- | BinLog row decoder----getBinLogRow :: [BinLogMeta] -> BitMap -> Get [BinLogValue]-getBinLogRow metas pmap = do-    let plen = B.foldl' (\acc word8 -> acc + popCount word8) 0 (fromBitMap pmap)-        maplen = (plen + 7) `shiftR` 3-    nullmap <- getByteString maplen-    go metas (BitMap nullmap) 0 pmap 0-  where-    go :: [BinLogMeta] -> BitMap -> Int -> BitMap -> Int -> Get [BinLogValue]-    go []     _       _       _     _    = pure []-    go (f:fs) nullmap nullpos pmap' ppos = do-        let ppos' = ppos + 1-        if isColumnSet pmap' ppos-        then do-            r <- if isColumnSet nullmap nullpos-                    then return BinLogNull-                    else getBinLogField f-            let nullpos' = nullpos + 1-            rest <- nullpos' `seq` ppos' `seq` go fs nullmap nullpos' pmap' ppos'-            return (rest `seq` (r : rest))-        else ppos' `seq` go fs nullmap nullpos pmap' ppos'-
− Database/MySQL/Connection.hs
@@ -1,275 +0,0 @@-{-|-Module      : Database.MySQL.Connection-Description : Connection managment-Copyright   : (c) Winterland, 2016-License     : BSD-Maintainer  : drkoster@qq.com-Stability   : experimental-Portability : PORTABLE--This is an internal module, the 'MySQLConn' type should not directly acessed to user.---}--module Database.MySQL.Connection where--import           Control.Applicative-import           Control.Exception               (Exception, bracketOnError,-                                                  throwIO, catch, SomeException)-import           Control.Monad-import qualified Crypto.Hash                     as Crypto-import qualified Data.Binary                     as Binary-import qualified Data.Binary.Put                 as Binary-import           Data.Bits-import qualified Data.ByteArray                  as BA-import           Data.ByteString                 (ByteString)-import qualified Data.ByteString                 as B-import qualified Data.ByteString.Lazy            as L-import qualified Data.ByteString.Unsafe          as B-import           Data.IORef                      (IORef, newIORef, readIORef,-                                                  writeIORef)-import           Data.Typeable-import           Data.Word-import           Database.MySQL.Protocol.Auth-import           Database.MySQL.Protocol.Command-import           Database.MySQL.Protocol.Packet-import           Network.Socket                  (HostName, PortNumber)-import           System.IO.Streams               (InputStream)-import qualified System.IO.Streams               as Stream-import qualified System.IO.Streams.TCP           as TCP-import qualified Data.Connection                 as TCP-------------------------------------------------------------------------------------- | 'MySQLConn' wrap both 'InputStream' and 'OutputStream' for MySQL 'Packet'.------ You shouldn't use one 'MySQLConn' in different thread, if you do that,--- consider protecting it with a @MVar@.----data MySQLConn = MySQLConn {-        mysqlRead        :: {-# UNPACK #-} !(InputStream  Packet)-    ,   mysqlWrite       :: (Packet -> IO ())-    ,   mysqlCloseSocket :: IO ()-    ,   isConsumed       :: {-# UNPACK #-} !(IORef Bool)-    }---- | Everything you need to establish a MySQL connection.------ To setup a TLS connection, use module "Database.MySQL.TLS" or "Database.MySQL.OpenSSL".----data ConnectInfo = ConnectInfo-    { ciHost     :: HostName-    , ciPort     :: PortNumber-    , ciDatabase :: ByteString-    , ciUser     :: ByteString-    , ciPassword :: ByteString-    , ciCharset  :: Word8-    } deriving Show---- | A simple 'ConnectInfo' targeting localhost with @user=root@ and empty password.------  Default charset is set to @utf8_general_ci@ to support older(< 5.5.3) MySQL versions,---  but be aware this is a partial utf8 encoding, you may want to use 'defaultConnectInfoMB4'---  instead to support full utf8 charset(emoji, etc.). You can query your server's support---  with @SELECT id, collation_name FROM information_schema.collations ORDER BY id;@----defaultConnectInfo :: ConnectInfo-defaultConnectInfo = ConnectInfo "127.0.0.1" 3306 "" "root" "" utf8_general_ci---- | 'defaultConnectInfo' with charset set to @utf8mb4_unicode_ci@------ This is recommanded on any MySQL server version >= 5.5.3.----defaultConnectInfoMB4 :: ConnectInfo-defaultConnectInfoMB4 = ConnectInfo "127.0.0.1" 3306 "" "root" "" utf8mb4_unicode_ci--utf8_general_ci :: Word8-utf8_general_ci = 33--utf8mb4_unicode_ci :: Word8-utf8mb4_unicode_ci = 224-------------------------------------------------------------------------------------- | Socket buffer size.------ maybe exposed to 'ConnectInfo' laster?----bUFSIZE :: Int-bUFSIZE = 16384---- | Establish a MySQL connection.----connect :: ConnectInfo -> IO MySQLConn-connect = fmap snd . connectDetail---- | Establish a MySQL connection with 'Greeting' back, so you can find server's version .etc.----connectDetail :: ConnectInfo -> IO (Greeting, MySQLConn)-connectDetail (ConnectInfo host port db user pass charset)-    = bracketOnError open TCP.close go-  where-    open  = connectWithBufferSize host port bUFSIZE-    go c  = do-        let is = TCP.source c-        is' <- decodeInputStream is-        p <- readPacket is'-        greet <- decodeFromPacket p-        let auth = mkAuth db user pass charset greet-        write c $ encodeToPacket 1 auth-        q <- readPacket is'-        if isOK q-        then do-            consumed <- newIORef True-            let waitNotMandatoryOK = catch-                    (void (waitCommandReply is'))           -- server will either reply an OK packet-                    ((\ _ -> return ()) :: SomeException -> IO ())   -- or directy close the connection-                conn = MySQLConn is'-                    (write c)-                    (writeCommand COM_QUIT (write c) >> waitNotMandatoryOK >> TCP.close c)-                    consumed-            return (greet, conn)-        else TCP.close c >> decodeFromPacket q >>= throwIO . ERRException--    connectWithBufferSize h p bs = TCP.connectSocket h p >>= TCP.socketToConnection bs-    write c a = TCP.send c $ Binary.runPut . Binary.put $ a--mkAuth :: ByteString -> ByteString -> ByteString -> Word8 -> Greeting -> Auth-mkAuth db user pass charset greet =-    let salt = greetingSalt1 greet `B.append` greetingSalt2 greet-        scambleBuf = scramble salt pass-    in Auth clientCap clientMaxPacketSize charset user scambleBuf db-  where-    scramble :: ByteString -> ByteString -> ByteString-    scramble salt pass'-        | B.null pass' = B.empty-        | otherwise   = B.pack (B.zipWith xor sha1pass withSalt)-        where sha1pass = sha1 pass'-              withSalt = sha1 (salt `B.append` sha1 sha1pass)--    sha1 :: ByteString -> ByteString-    sha1 = BA.convert . (Crypto.hash :: ByteString -> Crypto.Digest Crypto.SHA1)---- | A specialized 'decodeInputStream' here for speed-decodeInputStream :: InputStream ByteString -> IO (InputStream Packet)-decodeInputStream is = Stream.makeInputStream $ do-    bs <- Stream.readExactly 4 is-    let len =  fromIntegral (bs `B.unsafeIndex` 0)-           .|. fromIntegral (bs `B.unsafeIndex` 1) `shiftL` 8-           .|. fromIntegral (bs `B.unsafeIndex` 2) `shiftL` 16-        seqN = bs `B.unsafeIndex` 3-    body <- loopRead [] len is-    return . Just $ Packet len seqN body-  where-    loopRead acc 0 _  = return $! L.fromChunks (reverse acc)-    loopRead acc k is' = do-        bs <- Stream.read is'-        case bs of Nothing -> throwIO NetworkException-                   Just bs' -> do let l = fromIntegral (B.length bs')-                                  if l >= k-                                  then do-                                      let (a, rest) = B.splitAt (fromIntegral k) bs'-                                      unless (B.null rest) (Stream.unRead rest is')-                                      return $! L.fromChunks (reverse (a:acc))-                                  else do-                                      let k' = k - l-                                      k' `seq` loopRead (bs':acc) k' is'---- | Close a MySQL connection.----close :: MySQLConn -> IO ()-close (MySQLConn _ _ closeSocket _) = closeSocket---- | Send a 'COM_PING'.----ping :: MySQLConn -> IO OK-ping = flip command COM_PING------------------------------------------------------------------------------------- helpers---- | Send a 'Command' which don't return a resultSet.----command :: MySQLConn -> Command -> IO OK-command conn@(MySQLConn is os _ _) cmd = do-    guardUnconsumed conn-    writeCommand cmd os-    waitCommandReply is-{-# INLINE command #-}--waitCommandReply :: InputStream Packet -> IO OK-waitCommandReply is = do-    p <- readPacket is-    if  | isERR p -> decodeFromPacket p >>= throwIO . ERRException-        | isOK  p -> decodeFromPacket p-        | otherwise -> throwIO (UnexpectedPacket p)-{-# INLINE waitCommandReply #-}--waitCommandReplys :: InputStream Packet -> IO [OK]-waitCommandReplys is = do-    p <- readPacket is-    if  | isERR p -> decodeFromPacket p >>= throwIO . ERRException-        | isOK  p -> do ok <- decodeFromPacket p-                        if isThereMore ok-                        then (ok :) <$> waitCommandReplys is-                        else return [ok]-        | otherwise -> throwIO (UnexpectedPacket p)-{-# INLINE waitCommandReplys #-}--readPacket :: InputStream Packet -> IO Packet-readPacket is = Stream.read is >>= maybe-    (throwIO NetworkException)-    (\ p@(Packet len _ bs) -> if len < 16777215 then return p else go len [bs])-  where-    go len acc = Stream.read is >>= maybe-        (throwIO NetworkException)-        (\ (Packet len' seqN bs) -> do-            let len'' = len + len'-                acc' = bs:acc-            if len' < 16777215-            then return (Packet len'' seqN (L.concat . reverse $ acc'))-            else len'' `seq` go len'' acc'-        )-{-# INLINE readPacket #-}--writeCommand :: Command -> (Packet -> IO ()) -> IO ()-writeCommand a writePacket = let bs = Binary.runPut (putCommand a) in-    go (fromIntegral (L.length bs)) 0 bs writePacket-  where-    go len seqN bs writePacket' = do-        if len < 16777215-        then writePacket (Packet len seqN bs)-        else do-            let (bs', rest) = L.splitAt 16777215 bs-                seqN' = seqN + 1-                len'  = len - 16777215--            writePacket (Packet 16777215 seqN bs')-            seqN' `seq` len' `seq` go len' seqN' rest writePacket'-{-# INLINE writeCommand #-}--guardUnconsumed :: MySQLConn -> IO ()-guardUnconsumed (MySQLConn _ _ _ consumed) = do-    c <- readIORef consumed-    unless c (throwIO UnconsumedResultSet)-{-# INLINE guardUnconsumed #-}--writeIORef' :: IORef a -> a -> IO ()-writeIORef' ref x = x `seq` writeIORef ref x-{-# INLINE writeIORef' #-}------------------------------------------------------------------------------------- Exceptions--data NetworkException = NetworkException deriving (Typeable, Show)-instance Exception NetworkException--data UnconsumedResultSet = UnconsumedResultSet deriving (Typeable, Show)-instance Exception UnconsumedResultSet--data ERRException = ERRException ERR deriving (Typeable, Show)-instance Exception ERRException--data UnexpectedPacket = UnexpectedPacket Packet deriving (Typeable, Show)-instance Exception UnexpectedPacket-
− Database/MySQL/Protocol/Auth.hs
@@ -1,194 +0,0 @@-{-# LANGUAGE CPP #-}-{-# OPTIONS_GHC -funbox-strict-fields #-}--{-|-Module      : Database.MySQL.Protocol.Auth-Description : MySQL Auth Packets-Copyright   : (c) Winterland, 2016-License     : BSD-Maintainer  : drkoster@qq.com-Stability   : experimental-Portability : PORTABLE--Auth related packet.---}--module Database.MySQL.Protocol.Auth where--import           Control.Applicative-import           Control.Monad-import           Data.Binary-import           Data.Binary.Get-import           Data.Binary.Parser-import           Data.Binary.Put-import qualified Data.ByteString                as B-import           Data.ByteString.Char8          as BC-import           Data.Bits-import           Database.MySQL.Protocol.Packet------------------------------------------------------------------------------------- Authentications--#define CLIENT_LONG_PASSWORD                  0x00000001-#define CLIENT_FOUND_ROWS                     0x00000002-#define CLIENT_LONG_FLAG                      0x00000004-#define CLIENT_CONNECT_WITH_DB                0x00000008-#define CLIENT_NO_SCHEMA                      0x00000010-#define CLIENT_COMPRESS                       0x00000020-#define CLIENT_ODBC                           0x00000040-#define CLIENT_LOCAL_FILES                    0x00000080-#define CLIENT_IGNORE_SPACE                   0x00000100-#define CLIENT_PROTOCOL_41                    0x00000200-#define CLIENT_INTERACTIVE                    0x00000400-#define CLIENT_SSL                            0x00000800-#define CLIENT_IGNORE_SIGPIPE                 0x00001000-#define CLIENT_TRANSACTIONS                   0x00002000-#define CLIENT_RESERVED                       0x00004000-#define CLIENT_SECURE_CONNECTION              0x00008000-#define CLIENT_MULTI_STATEMENTS               0x00010000-#define CLIENT_MULTI_RESULTS                  0x00020000-#define CLIENT_PS_MULTI_RESULTS               0x00040000-#define CLIENT_PLUGIN_AUTH                    0x00080000-#define CLIENT_CONNECT_ATTRS                  0x00100000-#define CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA 0x00200000--data Greeting = Greeting-    { greetingProtocol :: !Word8-    , greetingVersion  :: !B.ByteString-    , greetingConnId   :: !Word32-    , greetingSalt1    :: !B.ByteString-    , greetingCaps     :: !Word32-    , greetingCharset  :: !Word8-    , greetingStatus   :: !Word16-    , greetingSalt2    :: !B.ByteString-    , greetingAuthPlugin :: !B.ByteString-    } deriving (Show, Eq)--putGreeting :: Greeting -> Put-putGreeting (Greeting pv sv cid salt1 cap charset st salt2 authPlugin) = do-    putWord8 pv-    putByteString sv-    putWord8 0x00-    putWord32le cid-    putByteString salt1-    let capL = fromIntegral cap .|. 0xFF-        capH = fromIntegral (cap `shiftR` 16) .|. 0xFF-    putWord16le capL-    putWord8 charset-    putWord16le st-    putWord16le capH-    putWord8 (fromIntegral $ B.length salt2)-    replicateM_ 10 (putWord8 0x00)-    when (cap .&. CLIENT_SECURE_CONNECTION /= 0)-        (putByteString salt2)-    when (cap .&. CLIENT_PLUGIN_AUTH /= 0)-        (putByteString authPlugin)--getGreeting :: Get Greeting-getGreeting = do-    pv <- getWord8-    sv <- getByteStringNul-    cid <- getWord32le-    salt1 <- getByteString 8-    skipN 1  -- 0x00-    capL <- getWord16le-    charset <- getWord8-    status <- getWord16le-    capH <- getWord16le-    let cap = fromIntegral capH `shiftL` 16 .|. fromIntegral capL-    authPluginLen <- getWord8   -- this will issue an unused warning, see the notes below-    skipN 10 -- 10 * 0x00-    salt2 <- if (cap .&. CLIENT_SECURE_CONNECTION) == 0-        then pure B.empty-        else getByteStringNul   -- This is different with the MySQL document here-                                -- The doc said we should expect a MAX(13, length of auth-plugin-data - 8)-                                -- length bytes, but doing so stop us from login-                                -- anyway 'getByteStringNul' works perfectly here.--    authPlugin <- if (cap .&. CLIENT_PLUGIN_AUTH) == 0-        then pure B.empty-        else getByteStringNul--    return (Greeting pv sv cid salt1 cap charset status salt2 authPlugin)--instance Binary Greeting where-    get = getGreeting-    put = putGreeting--data Auth = Auth-    { authCaps      :: !Word32-    , authMaxPacket :: !Word32-    , authCharset   :: !Word8-    , authName      :: !ByteString-    , authPassword  :: !ByteString-    , authSchema    :: !ByteString-    } deriving (Show, Eq)--getAuth :: Get Auth-getAuth = do-    a <- getWord32le-    m <- getWord32le-    c <- getWord8-    skipN 23-    n <- getByteStringNul-    return $ Auth a m c n B.empty B.empty--putAuth :: Auth -> Put-putAuth (Auth cap m c n p s) = do-    putWord32le cap-    putWord32le m-    putWord8 c-    replicateM_ 23 (putWord8 0x00)-    putByteString n >> putWord8 0x00-    putWord8 $ fromIntegral (B.length p)-    putByteString p-    putByteString s-    putWord8 0x00--instance Binary Auth where-    get = getAuth-    put = putAuth--data SSLRequest = SSLRequest-    { sslReqCaps      :: !Word32-    , sslReqMaxPacket :: !Word32-    , sslReqCharset   :: !Word8-    } deriving (Show, Eq)--getSSLRequest :: Get SSLRequest-getSSLRequest = SSLRequest <$> getWord32le <*> getWord32le <*> getWord8 <* skipN 23--putSSLRequest :: SSLRequest -> Put-putSSLRequest (SSLRequest cap m c) = do-    putWord32le cap-    putWord32le m-    putWord8 c-    replicateM_ 23 (putWord8 0x00)--instance Binary SSLRequest where-    get = getSSLRequest-    put = putSSLRequest------------------------------------------------------------------------------------- default Capability Flags--clientCap :: Word32-clientCap =  CLIENT_LONG_PASSWORD-                .|. CLIENT_LONG_FLAG-                .|. CLIENT_CONNECT_WITH_DB-                .|. CLIENT_IGNORE_SPACE-                .|. CLIENT_PROTOCOL_41-                .|. CLIENT_TRANSACTIONS-                .|. CLIENT_MULTI_STATEMENTS-                .|. CLIENT_SECURE_CONNECTION--clientMaxPacketSize :: Word32-clientMaxPacketSize = 0x00ffffff :: Word32---supportTLS :: Word32 -> Bool-supportTLS x = (x .&. CLIENT_SSL) /= 0--sslRequest :: Word8 -> SSLRequest-sslRequest charset = SSLRequest (clientCap .|. CLIENT_SSL) clientMaxPacketSize charset
− Database/MySQL/Protocol/ColumnDef.hs
@@ -1,176 +0,0 @@-{-# LANGUAGE CPP #-}-{-# OPTIONS_GHC -funbox-strict-fields #-}--{-|-Module      : Database.MySQL.Protocol.ColumnDef-Description : MySQL field type-Copyright   : (c) Winterland, 2016-License     : BSD-Maintainer  : drkoster@qq.com-Stability   : experimental-Portability : PORTABLE--Column definition(aka. field type).---}--module Database.MySQL.Protocol.ColumnDef where--import           Control.Applicative-import           Data.Binary-import           Data.Binary.Get-import           Data.Binary.Parser-import           Data.Binary.Put-import           Data.Bits                      ((.&.))-import           Data.ByteString                (ByteString)-import           Database.MySQL.Protocol.Packet-------------------------------------------------------------------------------------  Resultset---- | A description of a field (column) of a table.-data ColumnDef = ColumnDef-    { -- fieldCatalog :: !ByteString              -- ^ const 'def'-      columnDB        ::  !ByteString             -- ^ Database for table.-    , columnTable     ::  !ByteString             -- ^ Table of column, if column was a field.-    , columnOrigTable ::  !ByteString             -- ^ Original table name, if table was an alias.-    , columnName      ::  !ByteString             -- ^ Name of column.-    , columnOrigName  ::  !ByteString             -- ^ Original column name, if an alias.-    , columnCharSet   ::  !Word16                 -- ^ Character set number.-    , columnLength    ::  !Word32                 -- ^ Width of column (create length).-    , columnType      ::  !FieldType-    , columnFlags     ::  !Word16                 -- ^ Div flags.-    , columnDecimals  ::  !Word8                  -- ^ Number of decimals in field.-    } deriving (Show, Eq)--getField :: Get ColumnDef-getField = ColumnDef-        <$> (skipN 4                 -- const "def"-         *> getLenEncBytes)         -- db-        <*> getLenEncBytes          -- table-        <*> getLenEncBytes          -- origTable-        <*> getLenEncBytes          -- name-        <*> getLenEncBytes          -- origName-        <*  skipN 1                  -- const 0x0c-        <*> getWord16le             -- charset-        <*> getWord32le             -- length-        <*> getFieldType            -- type-        <*> getWord16le             -- flags-        <*> getWord8                -- decimals-        <* skipN 2                   -- const 0x00 0x00-{-# INLINE getField #-}--putField :: ColumnDef -> Put-putField (ColumnDef db tbl otbl name oname charset len typ flags dec) = do-    putLenEncBytes "def"-    putLenEncBytes db-    putLenEncBytes tbl-    putLenEncBytes otbl-    putLenEncBytes name-    putLenEncBytes oname-    putWord16le charset-    putWord32le len-    putFieldType typ-    putWord16le  flags-    putWord8 dec-    putWord16le 0X0000-{-# INLINE putField #-}--instance Binary ColumnDef where-    get = getField-    {-# INLINE get #-}-    put = putField-    {-# INLINE put #-}---- | @newtype@ around 'Word8' for represent @MySQL_TYPE@, We don't use sum type here for speed reason.----newtype FieldType = FieldType Word8 deriving (Show, Eq)--mySQLTypeDecimal, mySQLTypeTiny, mySQLTypeShort, mySQLTypeLong, mySQLTypeFloat :: FieldType-mySQLTypeDouble, mySQLTypeNull, mySQLTypeTimestamp, mySQLTypeLongLong, mySQLTypeInt24 :: FieldType-mySQLTypeDate, mySQLTypeTime, mySQLTypeDateTime, mySQLTypeYear, mySQLTypeNewDate, mySQLTypeVarChar :: FieldType-mySQLTypeBit, mySQLTypeTimestamp2, mySQLTypeDateTime2, mySQLTypeTime2, mySQLTypeNewDecimal :: FieldType-mySQLTypeEnum, mySQLTypeSet, mySQLTypeTinyBlob, mySQLTypeMediumBlob, mySQLTypeLongBlob :: FieldType-mySQLTypeBlob, mySQLTypeVarString, mySQLTypeString, mySQLTypeGeometry :: FieldType--mySQLTypeDecimal        = FieldType 0x00-mySQLTypeTiny           = FieldType 0x01-mySQLTypeShort          = FieldType 0x02-mySQLTypeLong           = FieldType 0x03-mySQLTypeFloat          = FieldType 0x04-mySQLTypeDouble         = FieldType 0x05-mySQLTypeNull           = FieldType 0x06-mySQLTypeTimestamp      = FieldType 0x07-mySQLTypeLongLong       = FieldType 0x08-mySQLTypeInt24          = FieldType 0x09-mySQLTypeDate           = FieldType 0x0a-mySQLTypeTime           = FieldType 0x0b-mySQLTypeDateTime       = FieldType 0x0c-mySQLTypeYear           = FieldType 0x0d-mySQLTypeNewDate        = FieldType 0x0e-mySQLTypeVarChar        = FieldType 0x0f-mySQLTypeBit            = FieldType 0x10-mySQLTypeTimestamp2     = FieldType 0x11-mySQLTypeDateTime2      = FieldType 0x12-mySQLTypeTime2          = FieldType 0x13-mySQLTypeNewDecimal     = FieldType 0xf6-mySQLTypeEnum           = FieldType 0xf7-mySQLTypeSet            = FieldType 0xf8-mySQLTypeTinyBlob       = FieldType 0xf9-mySQLTypeMediumBlob     = FieldType 0xfa-mySQLTypeLongBlob       = FieldType 0xfb-mySQLTypeBlob           = FieldType 0xfc-mySQLTypeVarString      = FieldType 0xfd-mySQLTypeString         = FieldType 0xfe-mySQLTypeGeometry       = FieldType 0xff--getFieldType :: Get FieldType-getFieldType = FieldType <$> getWord8-{-# INLINE getFieldType #-}--putFieldType :: FieldType -> Put-putFieldType (FieldType t) = putWord8 t-{-# INLINE putFieldType #-}--instance Binary FieldType where-    get = getFieldType-    {-# INLINE get #-}-    put = putFieldType-    {-# INLINE put #-}-------------------------------------------------------------------------------------  Field flags--#define NOT_NULL_FLAG         1-#define PRI_KEY_FLAG          2-#define UNIQUE_KEY_FLAG       4-#define MULT_KEY_FLAG         8-#define BLOB_FLAG             16-#define UNSIGNED_FLAG         32-#define ZEROFILL_FLAG         64-#define BINARY_FLAG           128-#define ENUM_FLAG             256-#define AUTO_INCREMENT_FLAG   512-#define TIMESTAMP_FLAG        1024-#define SET_FLAG              2048-#define NO_DEFAULT_VALUE_FLAG 4096-#define PART_KEY_FLAG         16384-#define NUM_FLAG              32768--flagNotNull, flagPrimaryKey, flagUniqueKey, flagMultipleKey, flagBlob, flagUnsigned, flagZeroFill :: Word16 -> Bool-flagBinary, flagEnum, flagAutoIncrement, flagTimeStamp, flagSet, flagNoDefaultValue, flagPartKey, flagNumeric :: Word16 -> Bool-flagNotNull        flags = flags .&. NOT_NULL_FLAG         == NOT_NULL_FLAG-flagPrimaryKey     flags = flags .&. PRI_KEY_FLAG          == PRI_KEY_FLAG-flagUniqueKey      flags = flags .&. UNIQUE_KEY_FLAG       == UNIQUE_KEY_FLAG-flagMultipleKey    flags = flags .&. MULT_KEY_FLAG         == MULT_KEY_FLAG-flagBlob           flags = flags .&. BLOB_FLAG             == BLOB_FLAG-flagUnsigned       flags = flags .&. UNSIGNED_FLAG         == UNSIGNED_FLAG-flagZeroFill       flags = flags .&. ZEROFILL_FLAG         == ZEROFILL_FLAG-flagBinary         flags = flags .&. BINARY_FLAG           == BINARY_FLAG-flagEnum           flags = flags .&. ENUM_FLAG             == ENUM_FLAG-flagAutoIncrement  flags = flags .&. AUTO_INCREMENT_FLAG   == AUTO_INCREMENT_FLAG-flagTimeStamp      flags = flags .&. TIMESTAMP_FLAG        == TIMESTAMP_FLAG-flagSet            flags = flags .&. SET_FLAG              == SET_FLAG-flagNoDefaultValue flags = flags .&. NO_DEFAULT_VALUE_FLAG == NO_DEFAULT_VALUE_FLAG-flagPartKey        flags = flags .&. PART_KEY_FLAG         == PART_KEY_FLAG-flagNumeric        flags = flags .&. NUM_FLAG              == NUM_FLAG
− Database/MySQL/Protocol/Command.hs
@@ -1,108 +0,0 @@-{-# OPTIONS_GHC -funbox-strict-fields #-}--{-|-Module      : Database.MySQL.Protocol.Command-Description : MySQL commands-Copyright   : (c) Winterland, 2016-License     : BSD-Maintainer  : drkoster@qq.com-Stability   : experimental-Portability : PORTABLE--Common MySQL commands supports.---}--module Database.MySQL.Protocol.Command where--import           Control.Applicative-import           Control.Monad-import           Data.Binary-import           Data.Binary.Get-import           Data.Binary.Parser-import           Data.Binary.Put-import           Data.ByteString                    (ByteString)-import qualified Data.ByteString.Lazy               as L-import           Database.MySQL.Protocol.MySQLValue-import           Database.MySQL.Protocol.Packet-------------------------------------------------------------------------------------  Commands--type StmtID = Word32---- | All support MySQL commands.----data Command-    = COM_QUIT                                    -- ^ 0x01-    | COM_INIT_DB        !ByteString              -- ^ 0x02-    | COM_QUERY          !L.ByteString            -- ^ 0x03-    | COM_PING                                    -- ^ 0x0E-    | COM_BINLOG_DUMP    !Word32 !Word16 !Word32 !ByteString -- ^ 0x12-            -- binlog-pos, flags(0x01), server-id, binlog-filename-    | COM_REGISTER_SLAVE !Word32 !ByteString !ByteString !ByteString !Word16 !Word32 !Word32 -- ^ 0x15-            -- server-id, slaves hostname, slaves user, slaves password,  slaves port, replication rank(ignored), master-id(usually 0)-    | COM_STMT_PREPARE   !L.ByteString            -- ^ 0x16 statement-    | COM_STMT_EXECUTE   !StmtID ![MySQLValue] !BitMap -- ^ 0x17 stmtId, params-    | COM_STMT_CLOSE     !StmtID                  -- ^ 0x19 stmtId-    | COM_STMT_RESET     !StmtID                  -- ^ 0x1A stmtId-    | COM_UNSUPPORTED-   deriving (Show, Eq)--putCommand :: Command -> Put-putCommand COM_QUIT              = putWord8 0x01-putCommand (COM_INIT_DB db)      = putWord8 0x02 >> putByteString db-putCommand (COM_QUERY q)         = putWord8 0x03 >> putLazyByteString q-putCommand COM_PING              = putWord8 0x0E-putCommand (COM_BINLOG_DUMP pos flags sid fname) = do-    putWord8 0x12-    putWord32le pos-    putWord16le flags-    putWord32le sid-    putByteString fname-putCommand (COM_REGISTER_SLAVE sid shost susr spass sport rrank mid) = do-    putWord8 0x15-    putWord32le sid-    putLenEncBytes shost-    putLenEncBytes susr-    putLenEncBytes spass-    putWord16le sport-    putWord32le rrank-    putWord32le mid-putCommand (COM_STMT_PREPARE stmt) = putWord8 0x16 >> putLazyByteString stmt-putCommand (COM_STMT_EXECUTE stid params nullmap) = do-    putWord8 0x17-    putWord32le stid-    putWord8 0x00 -- we only use @CURSOR_TYPE_NO_CURSOR@ here-    putWord32le 1 -- const 1-    unless (null params) $ do-        putByteString (fromBitMap nullmap)-        putWord8 0x01    -- always use new-params-bound-flag-        mapM_ putParamMySQLType params-        forM_ params putBinaryField--putCommand (COM_STMT_CLOSE stid) = putWord8 0x19 >> putWord32le stid-putCommand (COM_STMT_RESET stid) = putWord8 0x1A >> putWord32le stid-putCommand _                     = error "unsupported command"-------------------------------------------------------------------------------------  Prepared statment related---- | call 'isOK' with this packet return true-data StmtPrepareOK = StmtPrepareOK-    { stmtId        :: !StmtID-    , stmtColumnCnt :: !Int-    , stmtParamCnt  :: !Int-    , stmtWarnCnt   :: !Int-    } deriving (Show, Eq)--getStmtPrepareOK :: Get StmtPrepareOK-getStmtPrepareOK = do-    skipN 1 -- OK byte-    stmtid <- getWord32le-    cc <- fromIntegral <$> getWord16le-    pc <- fromIntegral <$> getWord16le-    skipN 1 -- reserved-    wc <- fromIntegral <$> getWord16le-    return (StmtPrepareOK stmtid cc pc wc)-{-# INLINE getStmtPrepareOK #-}
− Database/MySQL/Protocol/Escape.hs
@@ -1,107 +0,0 @@-{-# LANGUAGE BangPatterns #-}--{-|-Module      : Database.MySQL.Protocol.Escape-Description : Pure haskell mysql escape-Copyright   : (c) Winterland, 2016-License     : BSD-Maintainer  : drkoster@qq.com-Stability   : experimental-Portability : PORTABLE--This module provide escape machinery for bytes and text types.--reference: <http://dev.mysql.com/doc/refman/5.7/en/string-literals.html>--    * Escape Sequence	Character Represented by Sequence-    * \0              	An ASCII NUL (X'00') character-    * \'              	A single quote (“'”) character-    * \"              	A double quote (“"”) character-    * \b              	A backspace character-    * \n              	A newline (linefeed) character-    * \r              	A carriage return character-    * \t              	A tab character-    * \Z              	ASCII 26 (Control+Z); see note following the table-    * \\              	A backslash (“\”) character-    * \%              	A “%” character; see note following the table-    * \_              	A “_” character; see note following the table--The @\%@ and @\_@ sequences are used to search for literal instances of @%@ and @_@ in pattern-matching contexts where they would otherwise be interpreted as wildcard characters, so we won't auto escape @%@ or @_@ here.---}--module Database.MySQL.Protocol.Escape where--import           Data.ByteString          (ByteString)-import qualified Data.ByteString.Internal as B-import           Data.Text                (Text)-import qualified Data.Text.Array          as TA-import qualified Data.Text.Internal       as T-import           Data.Word-import           Foreign.ForeignPtr       (withForeignPtr)-import           Foreign.Ptr              (Ptr, minusPtr, plusPtr)-import           Foreign.Storable         (peek, poke, pokeByteOff)-import           GHC.IO                   (unsafeDupablePerformIO)--escapeText :: Text -> Text-escapeText (T.Text arr off len)-    | len <= 0  = T.empty-    | otherwise =-        let (arr', len') =  TA.run2 $ do-                marr <- TA.new (len * 2)-                loop arr (off + len) marr off 0-        in T.Text arr' 0 len'-  where-    escape c marr ix = do-        TA.unsafeWrite marr ix 92-        TA.unsafeWrite marr (ix+1) c--    loop oarr oend marr !ix !ix'-        | ix == oend = return (marr, ix')-        | otherwise  = do-            let c = TA.unsafeIndex oarr ix-                go1 = loop oarr oend marr (ix+1) (ix'+1)-                go2 = loop oarr oend marr (ix+1) (ix'+2)-            if  | c >= 0xD800 && c <= 0xDBFF  -> do let c2 = TA.unsafeIndex oarr (ix+1)-                                                    TA.unsafeWrite marr ix' c-                                                    TA.unsafeWrite marr (ix'+1) c2-                                                    loop oarr oend marr (ix+2) (ix'+2)-                | c == 0-                    || c == 39-                    || c == 34 -> escape c   marr ix' >> go2 -- \0 \' \"-                | c == 8       -> escape 98  marr ix' >> go2 -- \b-                | c == 10      -> escape 110 marr ix' >> go2 -- \n-                | c == 13      -> escape 114 marr ix' >> go2 -- \r-                | c == 9       -> escape 116 marr ix' >> go2 -- \t-                | c == 26      -> escape 90  marr ix' >> go2 -- \Z-                | c == 92      -> escape 92  marr ix' >> go2 -- \\--                | otherwise    -> TA.unsafeWrite marr ix' c >> go1--escapeBytes :: ByteString -> ByteString-escapeBytes (B.PS fp s len) = unsafeDupablePerformIO $ withForeignPtr fp $ \ a ->-    B.createUptoN (len * 2) $ \ b -> do-        b' <- loop (a `plusPtr` s) (a `plusPtr` s `plusPtr` len) b-        return (b' `minusPtr` b)-  where-    escape :: Word8 -> Ptr Word8 -> IO (Ptr Word8)-    escape c p = do-        poke p 92-        pokeByteOff p 1 c-        return (p `plusPtr` 2)--    loop !a aend !b-        | a == aend = return b-        | otherwise = do-            c <- peek a-            if  | c == 0-                    || c == 39-                    || c == 34 -> escape c   b >>= loop (a `plusPtr` 1) aend -- \0 \' \"-                | c == 8       -> escape 98  b >>= loop (a `plusPtr` 1) aend -- \b-                | c == 10      -> escape 110 b >>= loop (a `plusPtr` 1) aend -- \n-                | c == 13      -> escape 114 b >>= loop (a `plusPtr` 1) aend -- \r-                | c == 9       -> escape 116 b >>= loop (a `plusPtr` 1) aend -- \t-                | c == 26      -> escape 90  b >>= loop (a `plusPtr` 1) aend -- \Z-                | c == 92      -> escape 92  b >>= loop (a `plusPtr` 1) aend -- \\--                | otherwise    -> poke b c >> loop (a `plusPtr` 1) aend (b `plusPtr` 1)
− Database/MySQL/Protocol/MySQLValue.hs
@@ -1,567 +0,0 @@-{-# OPTIONS_GHC -funbox-strict-fields #-}--{-|-Module      : Database.MySQL.Protocol.MySQLValue-Description : Text and binary protocol-Copyright   : (c) Winterland, 2016-License     : BSD-Maintainer  : drkoster@qq.com-Stability   : experimental-Portability : PORTABLE--Core text and binary row decoder/encoder machinery.---}--module Database.MySQL.Protocol.MySQLValue-  ( -- * MySQLValue decoder and encoder-    MySQLValue(..)-  , putParamMySQLType-  , getTextField-  , putTextField-  , getTextRow-  , getTextRowVector-  , getBinaryField-  , putBinaryField-  , getBinaryRow-  , getBinaryRowVector-  -- * Internal utilities-  , getBits-  , BitMap(..)-  , isColumnSet-  , isColumnNull-  , makeNullMap-  ) where--import qualified Blaze.Text                         as Textual-import           Control.Applicative-import           Control.Monad-import           Data.Binary.Put-import           Data.Binary.Parser-import           Data.Binary.IEEE754-import           Data.Bits-import           Data.ByteString                    (ByteString)-import qualified Data.ByteString                    as B-import qualified Data.ByteString.Builder            as BB-import           Data.ByteString.Builder.Scientific (FPFormat (..),-                                                     formatScientificBuilder)-import qualified Data.ByteString.Char8              as BC-import qualified Data.ByteString.Lazy               as L-import qualified Data.ByteString.Lex.Fractional     as LexFrac-import qualified Data.ByteString.Lex.Integral       as LexInt-import qualified Data.ByteString.Unsafe             as B-import           Data.Fixed                         (Pico)-import           Data.Int-import           Data.Scientific                    (Scientific)-import           Data.Text                          (Text)-import qualified Data.Text.Encoding                 as T-import           Data.Time.Calendar                 (Day, fromGregorian,-                                                     toGregorian)-import           Data.Time.Format                   (defaultTimeLocale,-                                                     formatTime)-import           Data.Time.LocalTime                (LocalTime (..),-                                                     TimeOfDay (..))-import           Data.Word-import           Database.MySQL.Protocol.ColumnDef-import           Database.MySQL.Protocol.Escape-import           Database.MySQL.Protocol.Packet-import           GHC.Generics                       (Generic)-import qualified Data.Vector                        as V------------------------------------------------------------------------------------- | Data type mapping between MySQL values and haskell values.------ There're some subtle differences between MySQL values and haskell values:------ MySQL's @DATETIME@ and @TIMESTAMP@ are different on timezone handling:------  * DATETIME and DATE is just a represent of a calendar date, it has no timezone information involved,---  you always get the same value as you put no matter what timezone you're using with MySQL.------  * MySQL converts TIMESTAMP values from the current time zone to UTC for storage,---  and back from UTC to the current time zone for retrieval. If you put a TIMESTAMP with timezone A,---  then read it with timezone B, you may get different result because of this conversion, so always---  be careful about setting up the right timezone with MySQL, you can do it with a simple @SET time_zone = timezone;@---  for more info on timezone support, please read <http://dev.mysql.com/doc/refman/5.7/en/time-zone-support.html>------  So we use 'LocalTime' to present both @DATETIME@ and @TIMESTAMP@, but the local here is different.------ MySQL's @TIME@ type can present time of day, but also elapsed time or a time interval between two events.--- @TIME@ values may range from @-838:59:59@ to @838:59:59@, so 'MySQLTime' values consist of a sign and a--- 'TimeOfDay' whose hour part may exceeded 24. you can use @timeOfDayToTime@ to get the absolute time interval.------ Under MySQL >= 5.7, @DATETIME@, @TIMESTAMP@ and @TIME@ may contain fractional part, which matches haskell's--- precision.----data MySQLValue-    = MySQLDecimal       !Scientific   -- ^ DECIMAL, NEWDECIMAL-    | MySQLInt8U         !Word8        -- ^ Unsigned TINY-    | MySQLInt8          !Int8         -- ^ TINY-    | MySQLInt16U        !Word16       -- ^ Unsigned SHORT-    | MySQLInt16         !Int16        -- ^ SHORT-    | MySQLInt32U        !Word32       -- ^ Unsigned LONG, INT24-    | MySQLInt32         !Int32        -- ^ LONG, INT24-    | MySQLInt64U        !Word64       -- ^ Unsigned LONGLONG-    | MySQLInt64         !Int64        -- ^ LONGLONG-    | MySQLFloat         !Float        -- ^ IEEE 754 single precision format-    | MySQLDouble        !Double       -- ^ IEEE 754 double precision format-    | MySQLYear          !Word16       -- ^ YEAR-    | MySQLDateTime      !LocalTime    -- ^ DATETIME-    | MySQLTimeStamp     !LocalTime    -- ^ TIMESTAMP-    | MySQLDate          !Day              -- ^ DATE-    | MySQLTime          !Word8 !TimeOfDay -- ^ sign(0 = non-negative, 1 = negative) hh mm ss microsecond-                                           -- The sign is OPPOSITE to binlog one !!!-    | MySQLGeometry      !ByteString       -- ^ todo: parsing to something meanful-    | MySQLBytes         !ByteString-    | MySQLBit           !Word64-    | MySQLText          !Text-    | MySQLNull-  deriving (Show, Eq, Generic)---- | Put 'FieldType' and usigned bit(0x80/0x00) for 'MySQLValue's.----putParamMySQLType :: MySQLValue -> Put-putParamMySQLType (MySQLDecimal      _)  = putFieldType mySQLTypeDecimal  >> putWord8 0x00-putParamMySQLType (MySQLInt8U        _)  = putFieldType mySQLTypeTiny     >> putWord8 0x80-putParamMySQLType (MySQLInt8         _)  = putFieldType mySQLTypeTiny     >> putWord8 0x00-putParamMySQLType (MySQLInt16U       _)  = putFieldType mySQLTypeShort    >> putWord8 0x80-putParamMySQLType (MySQLInt16        _)  = putFieldType mySQLTypeShort    >> putWord8 0x00-putParamMySQLType (MySQLInt32U       _)  = putFieldType mySQLTypeLong     >> putWord8 0x80-putParamMySQLType (MySQLInt32        _)  = putFieldType mySQLTypeLong     >> putWord8 0x00-putParamMySQLType (MySQLInt64U       _)  = putFieldType mySQLTypeLongLong >> putWord8 0x80-putParamMySQLType (MySQLInt64        _)  = putFieldType mySQLTypeLongLong >> putWord8 0x00-putParamMySQLType (MySQLFloat        _)  = putFieldType mySQLTypeFloat    >> putWord8 0x00-putParamMySQLType (MySQLDouble       _)  = putFieldType mySQLTypeDouble   >> putWord8 0x00-putParamMySQLType (MySQLYear         _)  = putFieldType mySQLTypeYear     >> putWord8 0x80-putParamMySQLType (MySQLDateTime     _)  = putFieldType mySQLTypeDateTime >> putWord8 0x00-putParamMySQLType (MySQLTimeStamp    _)  = putFieldType mySQLTypeTimestamp>> putWord8 0x00-putParamMySQLType (MySQLDate         _)  = putFieldType mySQLTypeDate     >> putWord8 0x00-putParamMySQLType (MySQLTime       _ _)  = putFieldType mySQLTypeTime     >> putWord8 0x00-putParamMySQLType (MySQLBytes        _)  = putFieldType mySQLTypeBlob     >> putWord8 0x00-putParamMySQLType (MySQLGeometry     _)  = putFieldType mySQLTypeGeometry >> putWord8 0x00-putParamMySQLType (MySQLBit          _)  = putFieldType mySQLTypeBit      >> putWord8 0x00-putParamMySQLType (MySQLText         _)  = putFieldType mySQLTypeString   >> putWord8 0x00-putParamMySQLType MySQLNull              = putFieldType mySQLTypeNull     >> putWord8 0x00------------------------------------------------------------------------------------- | Text protocol decoder-getTextField :: ColumnDef -> Get MySQLValue-getTextField f-    | t == mySQLTypeNull            = pure MySQLNull-    | t == mySQLTypeDecimal-        || t == mySQLTypeNewDecimal = feedLenEncBytes t MySQLDecimal fracLexer-    | t == mySQLTypeTiny            = if isUnsigned then feedLenEncBytes t MySQLInt8U intLexer-                                                    else feedLenEncBytes t MySQLInt8 intLexer-    | t == mySQLTypeShort           = if isUnsigned then feedLenEncBytes t MySQLInt16U intLexer-                                                    else feedLenEncBytes t MySQLInt16 intLexer-    | t == mySQLTypeLong-        || t == mySQLTypeInt24      = if isUnsigned then feedLenEncBytes t MySQLInt32U intLexer-                                                    else feedLenEncBytes t MySQLInt32 intLexer-    | t == mySQLTypeLongLong        = if isUnsigned then feedLenEncBytes t MySQLInt64U intLexer-                                                    else feedLenEncBytes t MySQLInt64 intLexer-    | t == mySQLTypeFloat           = feedLenEncBytes t MySQLFloat fracLexer-    | t == mySQLTypeDouble          = feedLenEncBytes t MySQLDouble fracLexer-    | t == mySQLTypeYear            = feedLenEncBytes t MySQLYear intLexer-    | t == mySQLTypeTimestamp-        || t == mySQLTypeTimestamp2 = feedLenEncBytes t MySQLTimeStamp $ \ bs ->-                                          LocalTime <$> dateParser bs <*> timeParser (B.unsafeDrop 11 bs)-    | t == mySQLTypeDateTime-        || t == mySQLTypeDateTime2  = feedLenEncBytes t MySQLDateTime $ \ bs ->-                                          LocalTime <$> dateParser bs <*> timeParser (B.unsafeDrop 11 bs)-    | t == mySQLTypeDate-        || t == mySQLTypeNewDate    = feedLenEncBytes t MySQLDate dateParser-    | t == mySQLTypeTime-        || t == mySQLTypeTime2      = feedLenEncBytes t id $ \ bs ->-                                          if bs `B.unsafeIndex` 0 == 45  -- '-'-                                               then MySQLTime 1 <$> timeParser (B.unsafeDrop 1 bs)-                                               else MySQLTime 0 <$> timeParser bs--    | t == mySQLTypeGeometry        = MySQLGeometry <$> getLenEncBytes-    | t == mySQLTypeVarChar-        || t == mySQLTypeEnum-        || t == mySQLTypeSet-        || t == mySQLTypeTinyBlob-        || t == mySQLTypeMediumBlob-        || t == mySQLTypeLongBlob-        || t == mySQLTypeBlob-        || t == mySQLTypeVarString-        || t == mySQLTypeString     = (if isText then MySQLText . T.decodeUtf8 else MySQLBytes) <$> getLenEncBytes--    | t == mySQLTypeBit             = MySQLBit <$> (getBits =<< getLenEncInt)--    | otherwise                     = fail $ "Database.MySQL.Protocol.MySQLValue: missing text decoder for " ++ show t-  where-    t = columnType f-    isUnsigned = flagUnsigned (columnFlags f)-    isText = columnCharSet f /= 63-    intLexer bs = fst <$> LexInt.readSigned LexInt.readDecimal bs-    fracLexer bs = fst <$> LexFrac.readSigned LexFrac.readDecimal bs-    dateParser bs = do-        (yyyy, rest) <- LexInt.readDecimal bs-        (mm, rest') <- LexInt.readDecimal (B.unsafeTail rest)-        (dd, _) <- LexInt.readDecimal (B.unsafeTail rest')-        return (fromGregorian yyyy mm dd)--    timeParser bs = do-        (hh, rest) <- LexInt.readDecimal bs-        (mm, rest') <- LexInt.readDecimal (B.unsafeTail rest)-        (ss, _) <- LexFrac.readDecimal (B.unsafeTail rest')-        return (TimeOfDay hh mm ss)---feedLenEncBytes :: FieldType -> (t -> b) -> (ByteString -> Maybe t) -> Get b-feedLenEncBytes typ con parser = do-    bs <- getLenEncBytes-    case parser bs of-        Just v -> return (con v)-        Nothing -> fail $ "Database.MySQL.Protocol.MySQLValue: parsing " ++ show typ ++ " failed, \-                          \input: " ++ BC.unpack bs-{-# INLINE feedLenEncBytes #-}------------------------------------------------------------------------------------- | Text protocol encoder-putTextField :: MySQLValue -> Put-putTextField (MySQLDecimal    n) = putBuilder (formatScientificBuilder Fixed Nothing n)-putTextField (MySQLInt8U      n) = putBuilder (Textual.integral n)-putTextField (MySQLInt8       n) = putBuilder (Textual.integral n)-putTextField (MySQLInt16U     n) = putBuilder (Textual.integral n)-putTextField (MySQLInt16      n) = putBuilder (Textual.integral n)-putTextField (MySQLInt32U     n) = putBuilder (Textual.integral n)-putTextField (MySQLInt32      n) = putBuilder (Textual.integral n)-putTextField (MySQLInt64U     n) = putBuilder (Textual.integral n)-putTextField (MySQLInt64      n) = putBuilder (Textual.integral n)-putTextField (MySQLFloat      x) = putBuilder (Textual.float x)-putTextField (MySQLDouble     x) = putBuilder (Textual.double x)-putTextField (MySQLYear       n) = putBuilder (Textual.integral n)-putTextField (MySQLDateTime  dt) = putInQuotes $-                                      putByteString (BC.pack (formatTime defaultTimeLocale "%F %T%Q" dt))-putTextField (MySQLTimeStamp dt) = putInQuotes $-                                      putByteString (BC.pack (formatTime defaultTimeLocale "%F %T%Q" dt))-putTextField (MySQLDate       d) = putInQuotes $-                                      putByteString (BC.pack (formatTime defaultTimeLocale "%F" d))-putTextField (MySQLTime  sign t) = putInQuotes $ do-                                      when (sign == 1) (putCharUtf8 '-')-                                      putByteString (BC.pack (formatTime defaultTimeLocale "%T%Q" t))-                                      -- this works even for hour > 24-putTextField (MySQLGeometry  bs) = putInQuotes $ putByteString . escapeBytes $ bs-putTextField (MySQLBytes     bs) = putInQuotes $ putByteString . escapeBytes $ bs-putTextField (MySQLText       t) = putInQuotes $-                                      putByteString . T.encodeUtf8 . escapeText $ t-putTextField (MySQLBit        b) = do putBuilder "b\'"-                                      putBuilder . execPut $ putTextBits b-                                      putCharUtf8 '\''-  where-    putTextBits :: Word64 -> Put-    putTextBits word = forM_ [63,62..0] $ \ pos ->-            if word `testBit` pos then putCharUtf8 '1' else putCharUtf8 '0'-    {-# INLINE putTextBits #-}--putTextField MySQLNull           = putBuilder "NULL"--putInQuotes :: Put -> Put-putInQuotes p = putCharUtf8 '\'' >> p >> putCharUtf8 '\''-{-# INLINE putInQuotes #-}------------------------------------------------------------------------------------- | Text row decoder-getTextRow :: [ColumnDef] -> Get [MySQLValue]-getTextRow fs = forM fs $ \ f -> do-    p <- peek-    if p == 0xFB-    then skipN 1 >> return MySQLNull-    else getTextField f-{-# INLINE getTextRow #-}--getTextRowVector :: V.Vector ColumnDef -> Get (V.Vector MySQLValue)-getTextRowVector fs = V.forM fs $ \ f -> do-    p <- peek-    if p == 0xFB-    then skipN 1 >> return MySQLNull-    else getTextField f-{-# INLINE getTextRowVector #-}------------------------------------------------------------------------------------- | Binary protocol decoder-getBinaryField :: ColumnDef -> Get MySQLValue-getBinaryField f-    | t == mySQLTypeNull              = pure MySQLNull-    | t == mySQLTypeDecimal-        || t == mySQLTypeNewDecimal   = feedLenEncBytes t MySQLDecimal fracLexer-    | t == mySQLTypeTiny              = if isUnsigned then MySQLInt8U <$> getWord8-                                                      else MySQLInt8  <$> getInt8-    | t == mySQLTypeShort             = if isUnsigned then MySQLInt16U <$> getWord16le-                                                      else MySQLInt16  <$> getInt16le-    | t == mySQLTypeLong-        || t == mySQLTypeInt24        = if isUnsigned then MySQLInt32U <$> getWord32le-                                                      else MySQLInt32  <$> getInt32le-    | t == mySQLTypeYear              = MySQLYear . fromIntegral <$> getWord16le-    | t == mySQLTypeLongLong          = if isUnsigned then MySQLInt64U <$> getWord64le-                                                      else MySQLInt64  <$> getInt64le-    | t == mySQLTypeFloat             = MySQLFloat  <$> getFloatle-    | t == mySQLTypeDouble            = MySQLDouble <$> getDoublele-    | t == mySQLTypeTimestamp-        || t == mySQLTypeTimestamp2   = do-            n <- getLenEncInt-            case n of-               0 -> pure $ MySQLTimeStamp (LocalTime (fromGregorian 0 0 0) (TimeOfDay 0 0 0))-               4 -> do-                   d <- fromGregorian <$> getYear <*> getInt8' <*> getInt8'-                   pure $ MySQLTimeStamp (LocalTime d (TimeOfDay 0 0 0))-               7 -> do-                   d <- fromGregorian <$> getYear <*> getInt8' <*> getInt8'-                   td <- TimeOfDay <$> getInt8' <*> getInt8' <*> getSecond4-                   pure $ MySQLTimeStamp (LocalTime d td)-               11 -> do-                   d <- fromGregorian <$> getYear <*> getInt8' <*> getInt8'-                   td <- TimeOfDay <$> getInt8' <*> getInt8' <*> getSecond8-                   pure $ MySQLTimeStamp (LocalTime d td)-               _ -> fail "Database.MySQL.Protocol.MySQLValue: wrong TIMESTAMP length"-    | t == mySQLTypeDateTime-        || t == mySQLTypeDateTime2    = do-            n <- getLenEncInt-            case n of-               0 -> pure $ MySQLDateTime (LocalTime (fromGregorian 0 0 0) (TimeOfDay 0 0 0))-               4 -> do-                   d <- fromGregorian <$> getYear <*> getInt8' <*> getInt8'-                   pure $ MySQLDateTime (LocalTime d (TimeOfDay 0 0 0))-               7 -> do-                   d <- fromGregorian <$> getYear <*> getInt8' <*> getInt8'-                   td <- TimeOfDay <$> getInt8' <*> getInt8' <*> getSecond4-                   pure $ MySQLDateTime (LocalTime d td)-               11 -> do-                   d <- fromGregorian <$> getYear <*> getInt8' <*> getInt8'-                   td <- TimeOfDay <$> getInt8' <*> getInt8' <*> getSecond8-                   pure $ MySQLDateTime (LocalTime d td)-               _ -> fail "Database.MySQL.Protocol.MySQLValue: wrong DATETIME length"--    | t == mySQLTypeDate-        || t == mySQLTypeNewDate      = do-            n <- getLenEncInt-            case n of-               0 -> pure $ MySQLDate (fromGregorian 0 0 0)-               4 -> MySQLDate <$> (fromGregorian <$> getYear <*> getInt8' <*> getInt8')-               _ -> fail "Database.MySQL.Protocol.MySQLValue: wrong DATE length"--    | t == mySQLTypeTime-        || t == mySQLTypeTime2        = do-            n <- getLenEncInt-            case n of-               0 -> pure $ MySQLTime 0 (TimeOfDay 0 0 0)-               8 -> do-                   sign <- getWord8   -- is_negative(1 if minus, 0 for plus)-                   d <- fromIntegral <$> getWord32le-                   h <-  getInt8'-                   MySQLTime sign <$> (TimeOfDay (d*24 + h) <$> getInt8' <*> getSecond4)--               12 -> do-                   sign <- getWord8   -- is_negative(1 if minus, 0 for plus)-                   d <- fromIntegral <$> getWord32le-                   h <-  getInt8'-                   MySQLTime sign <$> (TimeOfDay (d*24 + h) <$> getInt8' <*> getSecond8)-               _ -> fail "Database.MySQL.Protocol.MySQLValue: wrong TIME length"--    | t == mySQLTypeGeometry          = MySQLGeometry <$> getLenEncBytes-    | t == mySQLTypeVarChar-        || t == mySQLTypeEnum-        || t == mySQLTypeSet-        || t == mySQLTypeTinyBlob-        || t == mySQLTypeMediumBlob-        || t == mySQLTypeLongBlob-        || t == mySQLTypeBlob-        || t == mySQLTypeVarString-        || t == mySQLTypeString       = if isText then MySQLText . T.decodeUtf8 <$> getLenEncBytes-                                                  else MySQLBytes <$> getLenEncBytes-    | t == mySQLTypeBit               = MySQLBit <$> (getBits =<< getLenEncInt)-    | otherwise                       = fail $ "Database.MySQL.Protocol.MySQLValue:\-                                               \ missing binary decoder for " ++ show t-  where-    t = columnType f-    isUnsigned = flagUnsigned (columnFlags f)-    isText = columnCharSet f /= 63-    fracLexer bs = fst <$> LexFrac.readSigned LexFrac.readDecimal bs-    getYear :: Get Integer-    getYear = fromIntegral <$> getWord16le-    getInt8' :: Get Int-    getInt8' = fromIntegral <$> getWord8-    getSecond4 :: Get Pico-    getSecond4 = realToFrac <$> getWord8-    getSecond8 :: Get Pico-    getSecond8 = realToFrac <$> do-        s <- getInt8'-        ms <- fromIntegral <$> getWord32le :: Get Int-        pure $! (realToFrac s + realToFrac ms / 1000000 :: Pico)----- | Get a bit sequence as a Word64------ Since 'Word64' has a @Bits@ instance, it's easier to deal with in haskell.----getBits :: Int -> Get Word64-getBits bytes =-    if  | bytes == 0 || bytes == 1 -> fromIntegral <$> getWord8-        | bytes == 2 -> fromIntegral <$> getWord16be-        | bytes == 3 -> fromIntegral <$> getWord24be-        | bytes == 4 -> fromIntegral <$> getWord32be-        | bytes == 5 -> fromIntegral <$> getWord40be-        | bytes == 6 -> fromIntegral <$> getWord48be-        | bytes == 7 -> fromIntegral <$> getWord56be-        | bytes == 8 -> fromIntegral <$> getWord64be-        | otherwise  -> fail $  "Database.MySQL.Protocol.MySQLValue: \-                                \wrong bit length size: " ++ show bytes-{-# INLINE getBits #-}-------------------------------------------------------------------------------------- | Binary protocol encoder-putBinaryField :: MySQLValue -> Put-putBinaryField (MySQLDecimal    n) = putLenEncBytes . L.toStrict . BB.toLazyByteString $-                                        formatScientificBuilder Fixed Nothing n-putBinaryField (MySQLInt8U      n) = putWord8 n-putBinaryField (MySQLInt8       n) = putWord8 (fromIntegral n)-putBinaryField (MySQLInt16U     n) = putWord16le n-putBinaryField (MySQLInt16      n) = putInt16le n-putBinaryField (MySQLInt32U     n) = putWord32le n-putBinaryField (MySQLInt32      n) = putInt32le n-putBinaryField (MySQLInt64U     n) = putWord64le n-putBinaryField (MySQLInt64      n) = putInt64le n-putBinaryField (MySQLFloat      x) = putFloatle x-putBinaryField (MySQLDouble     x) = putDoublele x-putBinaryField (MySQLYear       n) = putLenEncBytes . L.toStrict . BB.toLazyByteString $-                                        Textual.integral n  -- this's really weird, it's not documented anywhere-                                                            -- we must encode year into string in binary mode!-putBinaryField (MySQLTimeStamp (LocalTime date time)) = do putWord8 11    -- always put full-                                                           putBinaryDay date-                                                           putBinaryTime' time-putBinaryField (MySQLDateTime  (LocalTime date time)) = do putWord8 11    -- always put full-                                                           putBinaryDay date-                                                           putBinaryTime' time-putBinaryField (MySQLDate    d)    = do putWord8 4-                                        putBinaryDay d-putBinaryField (MySQLTime sign t)  = do putWord8 12    -- always put full-                                        putWord8 sign-                                        putBinaryTime t-putBinaryField (MySQLGeometry bs)  = putLenEncBytes bs-putBinaryField (MySQLBytes  bs)    = putLenEncBytes bs-putBinaryField (MySQLBit    word)  = do putWord8 8     -- always put full-                                        putWord64be word-putBinaryField (MySQLText    t)    = putLenEncBytes (T.encodeUtf8 t)-putBinaryField MySQLNull           = return ()--putBinaryDay :: Day -> Put-putBinaryDay d = do let (yyyy, mm, dd) = toGregorian d-                    putWord16le (fromIntegral yyyy)-                    putWord8 (fromIntegral mm)-                    putWord8 (fromIntegral dd)-{-# INLINE putBinaryDay #-}--putBinaryTime' :: TimeOfDay -> Put-putBinaryTime' (TimeOfDay hh mm ss) = do let s = floor ss-                                             ms = floor $ (ss - realToFrac s) * 1000000-                                         putWord8 (fromIntegral hh)-                                         putWord8 (fromIntegral mm)-                                         putWord8 s-                                         putWord32le ms-{-# INLINE putBinaryTime' #-}--putBinaryTime :: TimeOfDay -> Put-putBinaryTime (TimeOfDay hh mm ss) = do let s = floor ss-                                            ms = floor $ (ss - realToFrac s) * 1000000-                                            (d, h) = hh `quotRem` 24  -- hour may exceed 24 here-                                        putWord32le (fromIntegral d)-                                        putWord8 (fromIntegral h)-                                        putWord8 (fromIntegral mm)-                                        putWord8 s-                                        putWord32le ms-{-# INLINE putBinaryTime #-}------------------------------------------------------------------------------------- | Binary row decoder------ MySQL use a special null bitmap without offset = 2 here.----getBinaryRow :: [ColumnDef] -> Int -> Get [MySQLValue]-getBinaryRow fields flen = do-    skipN 1           -- 0x00-    let maplen = (flen + 7 + 2) `shiftR` 3-    nullmap <- BitMap <$> getByteString maplen-    go fields nullmap 0-  where-    go :: [ColumnDef] -> BitMap -> Int -> Get [MySQLValue]-    go []     _       _   = pure []-    go (f:fs) nullmap pos = do-        r <- if isColumnNull nullmap pos-                then return MySQLNull-                else getBinaryField f-        let pos' = pos + 1-        rest <- pos' `seq` go fs nullmap pos'-        return (r `seq` (r : rest))-{-# INLINE getBinaryRow #-}--getBinaryRowVector :: V.Vector ColumnDef -> Int -> Get (V.Vector MySQLValue)-getBinaryRowVector fields flen = do-    skipN 1           -- 0x00-    let maplen = (flen + 7 + 2) `shiftR` 3-    nullmap <- BitMap <$> getByteString maplen-    (`V.imapM` fields) $ \ pos f ->-        if isColumnNull nullmap pos then return MySQLNull else getBinaryField f-{-# INLINE getBinaryRowVector #-}------------------------------------------------------------------------------------- | Use 'ByteString' to present a bitmap.------ When used for represent bits values, the underlining 'ByteString' follows:------  * byteString: head       -> tail---  * bit:        high bit   -> low bit------ When used as a null-map/present-map, every bit inside a byte--- is mapped to a column, the mapping order is following:------  * byteString: head -> tail---  * column:     left -> right------ We don't use 'Int64' here because there maybe more than 64 columns.----newtype BitMap = BitMap { fromBitMap :: ByteString } deriving (Eq, Show)---- | Test if a column is set(binlog protocol).------ The number counts from left to right.----isColumnSet :: BitMap -> Int -> Bool-isColumnSet (BitMap bitmap) pos =-  let i = pos `unsafeShiftR` 3-      j = pos .&. 7-  in (bitmap `B.unsafeIndex` i) `testBit` j-{-# INLINE isColumnSet #-}---- | Test if a column is null(binary protocol).------ The number counts from left to right.----isColumnNull :: BitMap -> Int -> Bool-isColumnNull (BitMap nullmap) pos =-  let-    pos' = pos + 2-    i    = pos' `unsafeShiftR` 3-    j    = pos' .&. 7-  in (nullmap `B.unsafeIndex` i) `testBit` j-{-# INLINE isColumnNull #-}---- | Make a nullmap for params(binary protocol) without offset.----makeNullMap :: [MySQLValue] -> BitMap-makeNullMap values = BitMap . B.pack $ go values 0x00 0-  where-    go :: [MySQLValue] -> Word8 -> Int -> [Word8]-    go []             byte   8  = [byte]-    go vs             byte   8  = byte : go vs 0x00 0-    go []             byte   _  = [byte]-    go (MySQLNull:vs) byte pos  = let pos' = pos + 1-                                      byte' = byte .|. bit pos-                                  in pos' `seq` byte' `seq` go vs byte' pos'-    go (_        :vs) byte pos  = let pos' = pos + 1 in pos' `seq` go vs byte pos'------------------------------------------------------------------------------------- TODO: add helpers to parse mySQLTypeGEOMETRY--- reference: https://github.com/felixge/node-mysql/blob/master/lib/protocol/Parser.js
− Database/MySQL/Protocol/Packet.hs
@@ -1,300 +0,0 @@-{-# OPTIONS_GHC -funbox-strict-fields #-}--{-|-Module      : Database.MySQL.Protocol.Packet-Description : MySQL packet type and various helpers.-Copyright   : (c) Winterland, 2016-License     : BSD-Maintainer  : drkoster@qq.com-Stability   : experimental-Portability : PORTABLE--MySQL packet decoder&encoder, and varities utility.---}--module Database.MySQL.Protocol.Packet where--import           Control.Applicative-import           Control.Exception     (Exception (..), throwIO)-import           Data.Binary.Parser-import           Data.Binary.Put-import           Data.Binary           (Binary(..), encode)-import           Data.Bits-import qualified Data.ByteString       as B-import           Data.ByteString.Char8 as BC-import qualified Data.ByteString.Lazy  as L-import           Data.Int.Int24-import           Data.Int-import           Data.Word-import           Data.Typeable-import           Data.Word.Word24------------------------------------------------------------------------------------- | MySQL packet type----data Packet = Packet-    { pLen  :: !Int64-    , pSeqN :: !Word8-    , pBody :: !L.ByteString-    } deriving (Show, Eq)--putPacket :: Packet -> Put-putPacket (Packet len seqN body)  = do-    putWord24le (fromIntegral len)-    putWord8 seqN-    putLazyByteString body-{-# INLINE putPacket #-}--getPacket :: Get Packet-getPacket = do-    len <- fromIntegral <$> getWord24le-    seqN <- getWord8-    body <- getLazyByteString (fromIntegral len)-    return (Packet len seqN body)-{-# INLINE getPacket #-}--instance Binary Packet where-    put = putPacket-    {-# INLINE put #-}-    get = getPacket-    {-# INLINE get #-}--isERR :: Packet -> Bool-isERR p = L.index (pBody p) 0 == 0xFF-{-# INLINE isERR #-}--isOK :: Packet -> Bool-isOK p  = L.index (pBody p) 0 == 0x00-{-# INLINE isOK #-}--isEOF :: Packet -> Bool-isEOF p = L.index (pBody p) 0 == 0xFE-{-# INLINE isEOF #-}---- | Is there more packet to be read?------  https://dev.mysql.com/doc/internals/en/status-flags.html-isThereMore :: OK -> Bool-isThereMore p  = okStatus p .&. 0x08 /= 0-{-# INLINE isThereMore #-}---- | Decoding packet inside IO, throw 'DecodePacketException' on fail parsing,--- here we choose stability over correctness by omit incomplete consumed case:--- if we successful parse a packet, then we don't care if there're bytes left.----decodeFromPacket :: Binary a => Packet -> IO a-decodeFromPacket = getFromPacket get-{-# INLINE decodeFromPacket #-}--getFromPacket :: Get a -> Packet -> IO a-getFromPacket g (Packet _ _ body) = case parseDetailLazy g body of-    Left  (buf, offset, errmsg) -> throwIO (DecodePacketFailed buf offset errmsg)-    Right (_,   _,      r     ) -> return r-{-# INLINE getFromPacket #-}--data DecodePacketException = DecodePacketFailed ByteString ByteOffset String-  deriving (Typeable, Show)-instance Exception DecodePacketException--encodeToPacket :: Binary a => Word8 -> a -> Packet-encodeToPacket seqN payload =-    let s = encode payload-        l = L.length s-    in Packet (fromIntegral l) seqN s-{-# INLINE encodeToPacket #-}--putToPacket :: Word8 -> Put -> Packet-putToPacket seqN payload =-    let s = runPut payload-        l = L.length s-    in Packet (fromIntegral l) seqN s-{-# INLINE putToPacket #-}------------------------------------------------------------------------------------- OK, ERR, EOF---- | You may get interested in 'OK' packet because it provides information about--- successful operations.----data OK = OK-    { okAffectedRows :: !Int      -- ^ affected row number-    , okLastInsertID :: !Int      -- ^ last insert's ID-    , okStatus       :: !Word16-    , okWarningCnt   :: !Word16-    } deriving (Show, Eq)--getOK :: Get OK-getOK = OK <$ skipN 1-           <*> getLenEncInt-           <*> getLenEncInt-           <*> getWord16le-           <*> getWord16le-{-# INLINE getOK #-}--putOK :: OK -> Put-putOK (OK row lid stat wcnt) = do-    putWord8 0x00-    putLenEncInt row-    putLenEncInt lid-    putWord16le stat-    putWord16le wcnt-{-# INLINE putOK #-}--instance Binary OK where-    get = getOK-    {-# INLINE get #-}-    put = putOK-    {-# INLINE put #-}--data ERR = ERR-    { errCode  :: !Word16-    , errState :: !ByteString-    , errMsg   :: !ByteString-    } deriving (Show, Eq)--getERR :: Get ERR-getERR = ERR <$  skipN 1-             <*> getWord16le-             <*  skipN 1-             <*> getByteString 5-             <*> getRemainingByteString-{-# INLINE getERR #-}--putERR :: ERR -> Put-putERR (ERR code stat msg) = do-    putWord8 0xFF-    putWord16le code-    putWord8 35 -- '#'-    putByteString stat-    putByteString msg-{-# INLINE putERR #-}--instance Binary ERR where-    get = getERR-    {-# INLINE get #-}-    put = putERR-    {-# INLINE put #-}--data EOF = EOF-    { eofWarningCnt :: !Word16-    , eofStatus     :: !Word16-    } deriving (Show, Eq)--getEOF :: Get EOF-getEOF = EOF <$  skipN 1-             <*> getWord16le-             <*> getWord16le-{-# INLINE getEOF #-}--putEOF :: EOF -> Put-putEOF (EOF wcnt stat) = do-    putWord8 0xFE-    putWord16le wcnt-    putWord16le stat-{-# INLINE putEOF #-}--instance Binary EOF where-    get = getEOF-    {-# INLINE get #-}-    put = putEOF-    {-# INLINE put #-}-------------------------------------------------------------------------------------  Helpers--getByteStringNul :: Get ByteString-getByteStringNul = L.toStrict <$> getLazyByteStringNul-{-# INLINE getByteStringNul #-}--getRemainingByteString :: Get ByteString-getRemainingByteString = L.toStrict <$> getRemainingLazyByteString-{-# INLINE getRemainingByteString #-}--putLenEncBytes :: ByteString -> Put-putLenEncBytes c = do-    putLenEncInt (B.length c)-    putByteString c-{-# INLINE putLenEncBytes #-}--getLenEncBytes :: Get ByteString-getLenEncBytes = getLenEncInt >>= getByteString-{-# INLINE getLenEncBytes #-}---- | length encoded int--- https://dev.mysql.com/doc/internals/en/integer.html#packet-Protocol::LengthEncodedInteger-getLenEncInt:: Get Int-getLenEncInt = getWord8 >>= word2Len-  where-    word2Len l-         | l <  0xFB  = pure (fromIntegral l)-         | l == 0xFC  = fromIntegral <$> getWord16le-         | l == 0xFD  = fromIntegral <$> getWord24le-         | l == 0xFE  = fromIntegral <$> getWord64le-         | otherwise = fail $ "invalid length val " ++ show l-{-# INLINE getLenEncInt #-}--putLenEncInt:: Int -> Put-putLenEncInt x-         | x <  251      = putWord8 (fromIntegral x)-         | x < 65536     = putWord8 0xFC >> putWord16le (fromIntegral x)-         | x < 16777216  = putWord8 0xFD >> putWord24le (fromIntegral x)-         | otherwise     = putWord8 0xFE >> putWord64le (fromIntegral x)-{-# INLINE putLenEncInt #-}--putWord24le :: Word32 -> Put-putWord24le v = do-    putWord16le $ fromIntegral v-    putWord8 $ fromIntegral (v `shiftR` 16)-{-# INLINE putWord24le #-}--getWord24le :: Get Word32-getWord24le = do-    a <- fromIntegral <$> getWord16le-    b <- fromIntegral <$> getWord8-    return $! a .|. (b `shiftL` 16)-{-# INLINE getWord24le #-}--putWord48le :: Word64 -> Put-putWord48le v = do-    putWord32le $ fromIntegral v-    putWord16le $ fromIntegral (v `shiftR` 32)-{-# INLINE putWord48le #-}--getWord48le :: Get Word64-getWord48le = do-    a <- fromIntegral <$> getWord32le-    b <- fromIntegral <$> getWord16le-    return $! a .|. (b `shiftL` 32)-{-# INLINE getWord48le #-}--getWord24be :: Get Word24-getWord24be = do-    a <- fromIntegral <$> getWord16be-    b <- fromIntegral <$> getWord8-    return $! b .|. (a `shiftL` 8)-{-# INLINE getWord24be #-}--getInt24be :: Get Int24-getInt24be = do-    a <- fromIntegral <$> getWord16be-    b <- fromIntegral <$> getWord8-    return $! fromIntegral $ (b .|. (a `shiftL` 8) :: Word24)-{-# INLINE getInt24be #-}--getWord40be, getWord48be, getWord56be :: Get Word64-getWord40be = do-    a <- fromIntegral <$> getWord32be-    b <- fromIntegral <$> getWord8-    return $! (a `shiftL` 8) .|. b-getWord48be = do-    a <- fromIntegral <$> getWord32be-    b <- fromIntegral <$> getWord16be-    return $! (a `shiftL` 16) .|. b-getWord56be = do-    a <- fromIntegral <$> getWord32be-    b <- fromIntegral <$> getWord24be-    return $! (a `shiftL` 24) .|. b-{-# INLINE getWord40be #-}-{-# INLINE getWord48be #-}-{-# INLINE getWord56be #-}
− Database/MySQL/Query.hs
@@ -1,80 +0,0 @@-module Database.MySQL.Query where--import           Data.String               (IsString (..))-import           Control.Exception         (throw, Exception)-import           Data.Typeable-import qualified Data.ByteString.Lazy      as L-import qualified Data.ByteString.Lazy.Char8     as LC-import qualified Data.ByteString.Builder   as BB-import           Control.Arrow             (first)-import           Database.MySQL.Protocol.MySQLValue-import           Data.Binary.Put---- | Query string type borrowed from @mysql-simple@.------ This type is intended to make it difficult to--- construct a SQL query by concatenating string fragments, as that is--- an extremely common way to accidentally introduce SQL injection--- vulnerabilities into an application.------ This type is an instance of 'IsString', so the easiest way to--- construct a query is to enable the @OverloadedStrings@ language--- extension and then simply write the query in double quotes.------ The underlying type is a 'L.ByteString', and literal Haskell strings--- that contain Unicode characters will be correctly transformed to--- UTF-8.----newtype Query = Query { fromQuery :: L.ByteString } deriving (Eq, Ord, Typeable)--instance Show Query where-    show = show . fromQuery--instance Read Query where-    readsPrec i = fmap (first Query) . readsPrec i--instance IsString Query where-    fromString = Query . BB.toLazyByteString . BB.stringUtf8---- | A type to wrap a query parameter in to allow for single and multi-valued parameters.------ The behavior of 'Param' can be illustrated by following example:------ @---    render $ One (MySQLText "hello") = hello---    render $ Many [MySQLText "hello", MySQLText "world"] = hello, world---    render $ Many [] = null--- @------ So you can now write a query like this: @ SELECT * FROM test WHERE _id IN (?, 888) @--- and use 'Many' 'Param' to fill the hole. There's no equivalent for prepared statement sadly.----data Param = One  MySQLValue-           | Many [MySQLValue]---- | A type that may be used as a single parameter to a SQL query. Inspired from @mysql-simple@.-class QueryParam a where-    render :: a -> Put-    -- ^ Prepare a value for substitution into a query string.--instance QueryParam Param where-    render (One x)      = putTextField x-    render (Many [])    = putTextField MySQLNull-    render (Many (x:[]))= putTextField x-    render (Many (x:xs))= do putTextField x-                             mapM_ (\f -> putCharUtf8 ',' >> putTextField f) xs--instance QueryParam MySQLValue where-    render = putTextField--renderParams :: QueryParam p => Query -> [p] -> Query-renderParams (Query qry) params =-    let fragments = LC.split '?' qry-    in Query . runPut $ merge fragments params-  where-    merge [x]    []     = putLazyByteString x-    merge (x:xs) (y:ys) = putLazyByteString x >> render y >> merge xs ys-    merge _     _       = throw WrongParamsCount--data WrongParamsCount = WrongParamsCount deriving (Show, Typeable)-instance Exception WrongParamsCount
− Database/MySQL/TLS.hs
@@ -1,75 +0,0 @@-{-|-Module      : Database.MySQL.Connection-Description : TLS support for mysql-haskell via @tls@ package.-Copyright   : (c) Winterland, 2016-License     : BSD-Maintainer  : drkoster@qq.com-Stability   : experimental-Portability : PORTABLE--This module provides secure MySQL connection using 'tls' package, please make sure your certificate is v3 extension enabled.---}--module Database.MySQL.TLS (-      connect-    , connectDetail-    , module Data.TLSSetting-    ) where--import           Control.Exception              (bracketOnError, throwIO)-import qualified Data.Binary                    as Binary-import qualified Data.Binary.Put                as Binary-import qualified Data.Connection                as Conn-import           Data.IORef                     (newIORef)-import           Data.TLSSetting-import           Database.MySQL.Connection      hiding (connect, connectDetail)-import           Database.MySQL.Protocol.Auth-import           Database.MySQL.Protocol.Packet-import qualified Network.TLS                    as TLS-import qualified System.IO.Streams.TCP          as TCP-import qualified Data.Connection                as TCP-import qualified System.IO.Streams.TLS          as TLS-------------------------------------------------------------------------------------- | Provide a 'TLS.ClientParams' and a subject name to establish a TLS connection.----connect :: ConnectInfo -> (TLS.ClientParams, String) -> IO MySQLConn-connect c cp = fmap snd (connectDetail c cp)--connectDetail :: ConnectInfo -> (TLS.ClientParams, String) -> IO (Greeting, MySQLConn)-connectDetail (ConnectInfo host port db user pass charset) (cparams, subName) =-    bracketOnError (connectWithBufferSize host port bUFSIZE)-       (TCP.close) $ \ c -> do-            let is = TCP.source c-            is' <- decodeInputStream is-            p <- readPacket is'-            greet <- decodeFromPacket p-            if supportTLS (greetingCaps greet)-            then do-                let cparams' = cparams {-                            TLS.clientUseServerNameIndication = False-                        ,   TLS.clientServerIdentification = (subName, "")-                        }-                let (sock, sockAddr) = Conn.connExtraInfo c-                write c (encodeToPacket 1 $ sslRequest charset)-                bracketOnError (TLS.contextNew sock cparams')-                               ( \ ctx -> TLS.bye ctx >> TCP.close c ) $ \ ctx -> do-                    TLS.handshake ctx-                    tc <- TLS.tLsToConnection (ctx, sockAddr)-                    let tlsIs = TCP.source tc-                    tlsIs' <- decodeInputStream tlsIs-                    let auth = mkAuth db user pass charset greet-                    write tc (encodeToPacket 2 auth)-                    q <- readPacket tlsIs'-                    if isOK q-                    then do-                        consumed <- newIORef True-                        let conn = MySQLConn tlsIs' (write tc) (TCP.close tc) consumed-                        return (greet, conn)-                    else TCP.close c >> decodeFromPacket q >>= throwIO . ERRException-            else error "Database.MySQL.TLS: server doesn't support TLS connection"-  where-    connectWithBufferSize h p bs = TCP.connectSocket h p >>= TCP.socketToConnection bs-    write c a = TCP.send c $ Binary.runPut . Binary.put $ a
README.md view
@@ -2,27 +2,34 @@ =============  [![Hackage](https://img.shields.io/hackage/v/mysql-haskell.svg?style=flat)](http://hackage.haskell.org/package/mysql-haskell)-[![Build Status](https://travis-ci.org/winterland1989/mysql-haskell.svg)](https://travis-ci.org/winterland1989/mysql-haskell)  `mysql-haskell` is a MySQL driver written entirely in haskell. -<a href="http://www.genshuixue.com/"><img height=42 src='http://cdn.gsxservice.com/asset/img/logo-release2.png'></a> <a href="http://chordify.net/"><img height=42 src='https://chordify.net/img/about/slide_250_1.jpg'></a>-<a href="http://www.didichuxing.com/"><img height=42 src='http://www.didichuxing.com/images/icon02.png'></a>  Is it fast? ---------- -In short, `select`(decode) is about 1.5 times slower than pure c/c++ but 5 times faster than `mysql-simple`, `insert` (encode) is about 1.5 times slower than pure c/c++, there're many factors involved(tls, prepared statment, batch using multiple statement):+In short, reading is as fast as the C client (`libmysqlclient`) on one connection and up to 1.7 times slower with ten connections in parallel, and 5 to 8 times faster than `mysql-simple`. Writing one statement at a time is 1.2 to 1.4 times slower than C, and on par with C through `executeMany`. -<img src="https://github.com/winterland1989/mysql-haskell/blob/master/benchmark/result.png?raw=true" width="100%">+Median wall time in milliseconds over 10 runs, for 1 to 10 threads that each use their own connection: -Above figures showed the time to:+| threads                                   |   1 |   2 |   3 |   4 |   10 |+|-------------------------------------------|----:|----:|----:|----:|-----:|+| libmysqlclient select                     |  76 |  80 |  88 | 108 |  178 |+| mysql-haskell select                      |  80 |  95 | 106 | 130 |  309 |+| mysql-haskell select over TLS (`tls`)     | 120 | 130 | 148 | 172 |  436 |+| libmysqlclient select, prepared           |  82 |  86 |  94 | 120 |  179 |+| mysql-haskell select, prepared            |  77 |  88 | 102 | 124 |  275 |+| mysql-simple select                       | 462 | 504 | 578 | 698 | 2399 |+| libmysqlclient insert                     |  29 |  30 |  36 |  38 |   62 |+| mysql-haskell insert                      |  40 |  40 |  45 |  46 |   76 |+| mysql-haskell insert, `executeMany`       |  27 |  29 |  36 |  37 |   63 |+| mysql-haskell insert, prepared            |  38 |  38 |  45 |  46 |   82 | -* perform a "select * from employees" from a [sample table](https://github.com/datacharmer/test_db)-* insert 1000 rows into a 29-columns table per thread with auto-commit off.+Each thread either reads all 300,024 rows of the [sample employees table](https://github.com/datacharmer/test_db) with `select * from employees`, or inserts 1000 rows into a 29-column table with auto-commit off. The programs are the ones in `benchmark/`. They ran on mysql-haskell 1.3.3 with GHC 9.10.3 and `+RTS -N4`, against MySQL 8.0.45 and its own client library (mysql-simple goes through MariaDB Connector/C 3.3.5), with TLS off unless stated, on an AMD Ryzen AI 7 350 (October 2026). The server kept its data in RAM, so the inserts measure the clients rather than the disk. -The benchmarks are run by my MacBook Pro 13' 2015.+Leave the allocation area (`-A`) at its default when running with several capabilities: `-A128M` with `-N4` makes the runtime fault in a 128 MB allocation area for each capability it touches, which costs 35 to 280 ms per run in these benchmarks on every GHC version tried. Since GHC 8.2 it touches all four even when one thread does the work, where GHC 8.0, used for the 2016 figures this README showed until 2026, touched one or two. Without the flag, mysql-haskell 0.6.0.0 and 1.3.3 run plain and prepared selects equally fast, and 1.3.3 is 1.4 to 1.6 times faster over TLS.  Motivation ----------@@ -35,7 +42,7 @@  + no replication protocol support. -`mysql-haskell` is intended to solve these problems, and provide foundation for higher level libraries such as groundhog and persistent, so that accessing MySQL is both fast and easy in haskell.+`mysql-pure` is intended to solve these problems, and provide foundation for higher level libraries such as groundhog and persistent, so that accessing MySQL is both fast and easy in haskell.  Guide -----@@ -72,6 +79,20 @@  If you want to do batch inserting/deleting/updating, you can use `executeMany` to save considerable time. +To decode rows straight into your own types, skipping `MySQLValue`, give `queryRows_`, `queryRows` or `queryStmtRows` a decoder from `Database.MySQL.Decoder`. Each column is checked against its decoder once, before the first row:++```haskell+import qualified Database.MySQL.Decoder as Decode++data Person = Person { name :: Text, age :: Word32 }++person :: Decode.RowDecoder Person+person = Person <$> Decode.field Decode.text <*> Decode.field Decode.word32++    ...+    (defs, people) <- queryRows_ person conn "SELECT name, person_age FROM some_table"+```+ The `Database.MySQL.BinLog` module provides binlog listenning functions and row-based event decoder, following program will automatically get last binlog position, and print every row event it receives:  ```haskell@@ -106,8 +127,8 @@ Just use the old way:  ```bash-git clone https://github.com/winterland1989/mysql-haskell.git-cd mysql-haskell+git clone https://github.com/winterland1989/mysql-pure.git+cd mysql-pure cabal install --enable-tests --only-dependencies cabal build ```@@ -133,29 +154,21 @@  * Set `max_allowed_packet` to larger than 256M(for test large packet). -New features will be automatically tested by inspecting MySQL server's version, travis is keeping an eye on following combinations:--+ CABALVER=1.18 GHCVER=7.8.4  MYSQLVER=5.5-+ CABALVER=1.22 GHCVER=7.10.2 MYSQLVER=5.5-+ CABALVER=1.24 GHCVER=8.0.1  MYSQLVER=5.5-+ CABALVER=1.24 GHCVER=8.0.1  MYSQLVER=5.6-+ CABALVER=1.24 GHCVER=8.0.1  MYSQLVER=5.7--Please reference `.travis.yml` if you have problems with setting up test environment.--Enter benchmark directory and run `./bench.sh` to benchmark 1) c++ version 2) mysql-haskell 3) FFI version mysql, you may need to:+Enter benchmark directory and run `./bench.sh` to benchmark 1) c++ version 2) mysql-pure 3) FFI version mysql, you may need to:  + Modify `bench.sh`(change the include path) to get c++ version compiled.-+ Modify `mysql-haskell-bench.cabal`(change the openssl's lib path) to get haskell version compiled.++ Modify `mysql-pure-bench.cabal`(change the openssl's lib path) to get haskell version compiled. + Setup MySQL's TLS support, modify `MySQLHaskellOpenSSL.hs/MySQLHaskellTLS.hs` to change the CA file's path, and certificate's subject name.-+ Adjust rts options `-N` to get best results.++ Adjust rts options `-N` to get best results, and leave `-A` at its default (see "Is it fast?").  With `-N10` on my company's 24-core machine, binary protocol performs almost identical to c version! +The `.cabal` files in `benchmark/` date from 2016 and no longer build; the figures in "Is it fast?" came from the same programs compiled against the current library.+ Reference --------- -[MySQL official site](https://dev.mysql.com/doc/internals/en/) provided intensive document, but without following project, `mysql-haskell` may not be written at all:+[MySQL official site](https://dev.mysql.com/doc/internals/en/) provided intensive document, but without following project, `mysql-pure` may not be written at all:  + [mysql-binlog-connector-java](https://github.com/shyiko/mysql-binlog-connector-java) 
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
+ binary-parser-bench/Aeson.hs view
@@ -0,0 +1,361 @@+{-# LANGUAGE BangPatterns, CPP, OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-unused-binds #-}++module Aeson+    (+      aeson+    , aesonLazy+    , value'+    ) where++import Data.ByteString.Builder+  (Builder, byteString, toLazyByteString, charUtf8, word8)++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((*>), (<$>), (<*), pure)+import Data.Monoid (mappend, mempty)+#endif++import Control.Applicative (liftA2)+import Control.DeepSeq (NFData(..))+import Control.Monad (forM)+import Data.Attoparsec.ByteString.Char8 (Parser, char, endOfInput, scientific,+                                         skipSpace, string)+import Data.Bits ((.|.), shiftL)+import Data.ByteString (ByteString)+import Data.Char (chr)+import Data.List (sort)+import Data.Scientific (Scientific)+import Data.Text (Text)+import Data.Text.Encoding (decodeUtf8')+import Data.Vector as Vector (Vector, foldl', fromList)+import Data.Word (Word8)+import System.Directory (getDirectoryContents, doesDirectoryExist)+import System.FilePath ((</>), dropExtension)+import qualified Data.Attoparsec.ByteString as A+import qualified Data.Attoparsec.Lazy as L+import qualified Data.Attoparsec.Zepto as Z+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Unsafe as B+import qualified Data.HashMap.Strict as H+import Criterion.Main+import Common (pathTo)++#define BACKSLASH 92+#define CLOSE_CURLY 125+#define CLOSE_SQUARE 93+#define COMMA 44+#define DOUBLE_QUOTE 34+#define OPEN_CURLY 123+#define OPEN_SQUARE 91+#define C_0 48+#define C_9 57+#define C_A 65+#define C_F 70+#define C_a 97+#define C_f 102+#define C_n 110+#define C_t 116++data Result a = Error String+              | Success a+                deriving (Eq, Show)+++-- | A JSON \"object\" (key\/value map).+type Object = H.HashMap Text Value++-- | A JSON \"array\" (sequence).+type Array = Vector Value++-- | A JSON value represented as a Haskell value.+data Value = Object !Object+           | Array !Array+           | String !Text+           | Number !Scientific+           | Bool !Bool+           | Null+             deriving (Eq, Show)++instance NFData Value where+    rnf (Object o) = rnf o+    rnf (Array a)  = Vector.foldl' (\x y -> rnf y `seq` x) () a+    rnf (String s) = rnf s+    rnf (Number n) = rnf n+    rnf (Bool b)   = rnf b+    rnf Null       = ()++-- | Parse a top-level JSON value.  This must be either an object or+-- an array, per RFC 4627.+--+-- The conversion of a parsed value to a Haskell value is deferred+-- until the Haskell value is needed.  This may improve performance if+-- only a subset of the results of conversions are needed, but at a+-- cost in thunk allocation.+json :: Parser Value+json = json_ object_ array_++-- | Parse a top-level JSON value.  This must be either an object or+-- an array, per RFC 4627.+--+-- This is a strict version of 'json' which avoids building up thunks+-- during parsing; it performs all conversions immediately.  Prefer+-- this version if most of the JSON data needs to be accessed.+json' :: Parser Value+json' = json_ object_' array_'++json_ :: Parser Value -> Parser Value -> Parser Value+json_ obj ary = do+  w <- skipSpace *> A.satisfy (\w -> w == OPEN_CURLY || w == OPEN_SQUARE)+  if w == OPEN_CURLY+    then obj+    else ary+{-# INLINE json_ #-}++object_ :: Parser Value+object_ = {-# SCC "object_" #-} Object <$> objectValues jstring value++object_' :: Parser Value+object_' = {-# SCC "object_'" #-} do+  !vals <- objectValues jstring' value'+  return (Object vals)+ where+  jstring' = do+    !s <- jstring+    return s++objectValues :: Parser Text -> Parser Value -> Parser (H.HashMap Text Value)+objectValues str val = do+  skipSpace+  let pair = liftA2 (,) (str <* skipSpace) (char ':' *> skipSpace *> val)+  H.fromList <$> commaSeparated pair CLOSE_CURLY+{-# INLINE objectValues #-}++array_ :: Parser Value+array_ = {-# SCC "array_" #-} Array <$> arrayValues value++array_' :: Parser Value+array_' = {-# SCC "array_'" #-} do+  !vals <- arrayValues value'+  return (Array vals)++commaSeparated :: Parser a -> Word8 -> Parser [a]+commaSeparated item endByte = do+  w <- A.peekWord8'+  if w == endByte+    then A.anyWord8 >> return []+    else loop+  where+    loop = do+      v <- item <* skipSpace+      ch <- A.satisfy $ \w -> w == COMMA || w == endByte+      if ch == COMMA+        then skipSpace >> (v:) <$> loop+        else return [v]+{-# INLINE commaSeparated #-}++arrayValues :: Parser Value -> Parser (Vector Value)+arrayValues val = do+  skipSpace+  Vector.fromList <$> commaSeparated val CLOSE_SQUARE+{-# INLINE arrayValues #-}++-- | Parse any JSON value.  You should usually 'json' in preference to+-- this function, as this function relaxes the object-or-array+-- requirement of RFC 4627.+--+-- In particular, be careful in using this function if you think your+-- code might interoperate with Javascript.  A na&#xef;ve Javascript+-- library that parses JSON data using @eval@ is vulnerable to attack+-- unless the encoded data represents an object or an array.  JSON+-- implementations in other languages conform to that same restriction+-- to preserve interoperability and security.+value :: Parser Value+value = do+  w <- A.peekWord8'+  case w of+    DOUBLE_QUOTE  -> A.anyWord8 *> (String <$> jstring_)+    OPEN_CURLY    -> A.anyWord8 *> object_+    OPEN_SQUARE   -> A.anyWord8 *> array_+    C_f           -> string "false" *> pure (Bool False)+    C_t           -> string "true" *> pure (Bool True)+    C_n           -> string "null" *> pure Null+    _              | w >= 48 && w <= 57 || w == 45+                  -> Number <$> scientific+      | otherwise -> fail "not a valid json value"++-- | Strict version of 'value'. See also 'json''.+value' :: Parser Value+value' = do+  w <- A.peekWord8'+  case w of+    DOUBLE_QUOTE  -> do+                     !s <- A.anyWord8 *> jstring_+                     return (String s)+    OPEN_CURLY    -> A.anyWord8 *> object_'+    OPEN_SQUARE   -> A.anyWord8 *> array_'+    C_f           -> string "false" *> pure (Bool False)+    C_t           -> string "true" *> pure (Bool True)+    C_n           -> string "null" *> pure Null+    _              | w >= 48 && w <= 57 || w == 45+                  -> do+                     !n <- scientific+                     return (Number n)+      | otherwise -> fail "not a valid json value"++-- | Parse a quoted JSON string.+jstring :: Parser Text+jstring = A.word8 DOUBLE_QUOTE *> jstring_++-- | Parse a string without a leading quote.+jstring_ :: Parser Text+jstring_ = {-# SCC "jstring_" #-} do+  s <- A.scan False $ \s c -> if s then Just False+                                   else if c == DOUBLE_QUOTE+                                        then Nothing+                                        else Just (c == BACKSLASH)+  _ <- A.word8 DOUBLE_QUOTE+  s1 <- if BACKSLASH `B.elem` s+        then case Z.parse unescape s of+            Right r  -> return r+            Left err -> fail err+         else return s++  case decodeUtf8' s1 of+      Right r  -> return r+      Left err -> fail $ show err++{-# INLINE jstring_ #-}++unescape :: Z.Parser ByteString+unescape = toByteString <$> go mempty where+  go acc = do+    h <- Z.takeWhile (/=BACKSLASH)+    let rest = do+          start <- Z.take 2+          let !slash = B.unsafeHead start+              !t = B.unsafeIndex start 1+              escape = case B.findIndex (==t) "\"\\/ntbrfu" of+                         Just i -> i+                         _      -> 255+          if slash /= BACKSLASH || escape == 255+            then fail "invalid JSON escape sequence"+            else do+            let cont m = go (acc `mappend` byteString h `mappend` m)+                {-# INLINE cont #-}+            if t /= 117 -- 'u'+              then cont (word8 (B.unsafeIndex mapping escape))+              else do+                   a <- hexQuad+                   if a < 0xd800 || a > 0xdfff+                     then cont (charUtf8 (chr a))+                     else do+                       b <- Z.string "\\u" *> hexQuad+                       if a <= 0xdbff && b >= 0xdc00 && b <= 0xdfff+                         then let !c = ((a - 0xd800) `shiftL` 10) ++                                       (b - 0xdc00) + 0x10000+                              in cont (charUtf8 (chr c))+                         else fail "invalid UTF-16 surrogates"+    done <- Z.atEnd+    if done+      then return (acc `mappend` byteString h)+      else rest+  mapping = "\"\\/\n\t\b\r\f"++hexQuad :: Z.Parser Int+hexQuad = do+  s <- Z.take 4+  let hex n | w >= C_0 && w <= C_9 = w - C_0+            | w >= C_a && w <= C_f = w - 87+            | w >= C_A && w <= C_F = w - 55+            | otherwise          = 255+        where w = fromIntegral $ B.unsafeIndex s n+      a = hex 0; b = hex 1; c = hex 2; d = hex 3+  if (a .|. b .|. c .|. d) /= 255+    then return $! d .|. (c `shiftL` 4) .|. (b `shiftL` 8) .|. (a `shiftL` 12)+    else fail "invalid hex escape"++decodeWith :: Parser Value -> (Value -> Result a) -> L.ByteString -> Maybe a+decodeWith p to s =+    case L.parse p s of+      L.Done _ v -> case to v of+                      Success a -> Just a+                      _         -> Nothing+      _          -> Nothing+{-# INLINE decodeWith #-}++decodeStrictWith :: Parser Value -> (Value -> Result a) -> B.ByteString+                 -> Maybe a+decodeStrictWith p to s =+    case either Error to (A.parseOnly p s) of+      Success a -> Just a+      Error _ -> Nothing+{-# INLINE decodeStrictWith #-}++eitherDecodeWith :: Parser Value -> (Value -> Result a) -> L.ByteString+                 -> Either String a+eitherDecodeWith p to s =+    case L.parse p s of+      L.Done _ v -> case to v of+                      Success a -> Right a+                      Error msg -> Left msg+      L.Fail _ _ msg -> Left msg+{-# INLINE eitherDecodeWith #-}++eitherDecodeStrictWith :: Parser Value -> (Value -> Result a) -> B.ByteString+                       -> Either String a+eitherDecodeStrictWith p to s =+    case either Error to (A.parseOnly p s) of+      Success a -> Right a+      Error msg -> Left msg+{-# INLINE eitherDecodeStrictWith #-}++-- $lazy+--+-- The 'json' and 'value' parsers decouple identification from+-- conversion.  Identification occurs immediately (so that an invalid+-- JSON document can be rejected as early as possible), but conversion+-- to a Haskell value is deferred until that value is needed.+--+-- This decoupling can be time-efficient if only a smallish subset of+-- elements in a JSON value need to be inspected, since the cost of+-- conversion is zero for uninspected elements.  The trade off is an+-- increase in memory usage, due to allocation of thunks for values+-- that have not yet been converted.++-- $strict+--+-- The 'json'' and 'value'' parsers combine identification with+-- conversion.  They consume more CPU cycles up front, but have a+-- smaller memory footprint.++-- | Parse a top-level JSON value followed by optional whitespace and+-- end-of-input.  See also: 'json'.+jsonEOF :: Parser Value+jsonEOF = json <* skipSpace <* endOfInput++-- | Parse a top-level JSON value followed by optional whitespace and+-- end-of-input.  See also: 'json''.+jsonEOF' :: Parser Value+jsonEOF' = json' <* skipSpace <* endOfInput++toByteString :: Builder -> ByteString+toByteString = L.toStrict . toLazyByteString+{-# INLINE toByteString #-}++aeson :: IO [Benchmark]+aeson = do+  path <- pathTo "json-data"+  names <- sort . filter (`notElem` [".", ".."]) <$> getDirectoryContents path+  forM names $ \name -> do+    bs <- B.readFile (path </> name)+    return . bench ("attoparsec/" ++ dropExtension name) $ nf (A.parseOnly jsonEOF') bs++aesonLazy :: IO [Benchmark]+aesonLazy = do+  path <- pathTo "json-data"+  names <- sort . filter (`notElem` [".", ".."]) <$> getDirectoryContents path+  forM names $ \name -> do+    bs <- L.readFile (path </> name)+    return . bench ("attoparsec/lazy-bytestring/" ++ dropExtension name) $ nf (L.parse jsonEOF') bs
+ binary-parser-bench/AesonBP.hs view
@@ -0,0 +1,325 @@+{-# LANGUAGE BangPatterns, CPP, OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-unused-binds #-}++module AesonBP+    (+      aeson+    , aesonLazy+    , value'+    ) where++import Data.ByteString.Builder+  (Builder, byteString, toLazyByteString, charUtf8, word8)++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((*>), (<$>), (<*), pure)+import Data.Monoid (mappend, mempty)+#endif++import Control.Applicative (liftA2)+import Control.DeepSeq (NFData(..))+import Control.Monad (forM)+import Data.Bits ((.|.), shiftL)+import Data.ByteString (ByteString)+import Data.Char (chr)+import Data.List (sort)+import Data.Scientific (Scientific)+import Data.Text (Text)+import Data.Text.Encoding (decodeUtf8')+import Data.Vector as Vector (Vector, foldl', fromList)+import Data.Word (Word8)+import System.Directory (getDirectoryContents, doesDirectoryExist)+import System.FilePath ((</>), dropExtension)+import qualified Data.Attoparsec.Zepto as Z+import Data.Binary.Get (Get)+import qualified Data.Binary.Parser as BP+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Unsafe as B+import qualified Data.HashMap.Strict as H+import Criterion.Main+import Common (pathTo)++#define BACKSLASH 92+#define CLOSE_CURLY 125+#define CLOSE_SQUARE 93+#define COMMA 44+#define COLON 58+#define DOUBLE_QUOTE 34+#define OPEN_CURLY 123+#define OPEN_SQUARE 91+#define C_0 48+#define C_9 57+#define C_A 65+#define C_F 70+#define C_a 97+#define C_f 102+#define C_n 110+#define C_t 116++data Result a = Error String+              | Success a+                deriving (Eq, Show)+++-- | A JSON \"object\" (key\/value map).+type Object = H.HashMap Text Value++-- | A JSON \"array\" (sequence).+type Array = Vector Value++-- | A JSON value represented as a Haskell value.+data Value = Object !Object+           | Array !Array+           | String !Text+           | Number !Scientific+           | Bool !Bool+           | Null+             deriving (Eq, Show)++instance NFData Value where+    rnf (Object o) = rnf o+    rnf (Array a)  = Vector.foldl' (\x y -> rnf y `seq` x) () a+    rnf (String s) = rnf s+    rnf (Number n) = rnf n+    rnf (Bool b)   = rnf b+    rnf Null       = ()++-- | Parse a top-level JSON value.  This must be either an object or+-- an array, per RFC 4627.+--+-- The conversion of a parsed value to a Haskell value is deferred+-- until the Haskell value is needed.  This may improve performance if+-- only a subset of the results of conversions are needed, but at a+-- cost in thunk allocation.+json :: Get Value+json = json_ object_ array_++-- | Parse a top-level JSON value.  This must be either an object or+-- an array, per RFC 4627.+--+-- This is a strict version of 'json' which avoids building up thunks+-- during parsing; it performs all conversions immediately.  Prefer+-- this version if most of the JSON data needs to be accessed.+json' :: Get Value+json' = json_ object_' array_'++json_ :: Get Value -> Get Value -> Get Value+json_ obj ary = do+  w <- BP.skipSpaces *> BP.satisfy (\w -> w == OPEN_CURLY || w == OPEN_SQUARE)+  if w == OPEN_CURLY+    then obj+    else ary+{-# INLINE json_ #-}++object_ :: Get Value+object_ = {-# SCC "object_" #-} Object <$> objectValues jstring value++object_' :: Get Value+object_' = {-# SCC "object_'" #-} do+  !vals <- objectValues jstring' value'+  return (Object vals)+ where+  jstring' = do+    !s <- jstring+    return s++objectValues :: Get Text -> Get Value -> Get (H.HashMap Text Value)+objectValues str val = do+  BP.skipSpaces+  let pair = liftA2 (,) (str <* BP.skipSpaces) (BP.word8 COLON *> BP.skipSpaces *> val)+  H.fromList <$> commaSeparated pair CLOSE_CURLY+{-# INLINE objectValues #-}++array_ :: Get Value+array_ = {-# SCC "array_" #-} Array <$> arrayValues value++array_' :: Get Value+array_' = {-# SCC "array_'" #-} do+  !vals <- arrayValues value'+  return (Array vals)++commaSeparated :: Get a -> Word8 -> Get [a]+commaSeparated item endByte = do+  w <- BP.peek+  if w == endByte+    then BP.skipN 1 >> return []+    else loop+  where+    loop = do+      v <- item <* BP.skipSpaces+      ch <- BP.satisfy $ \w -> w == COMMA || w == endByte+      if ch == COMMA+        then BP.skipSpaces >> (v:) <$> loop+        else return [v]+{-# INLINE commaSeparated #-}++arrayValues :: Get Value -> Get (Vector Value)+arrayValues val = do+  BP.skipSpaces+  Vector.fromList <$> commaSeparated val CLOSE_SQUARE+{-# INLINE arrayValues #-}++-- | Parse any JSON value.  You should usually 'json' in preference to+-- this function, as this function relaxes the object-or-array+-- requirement of RFC 4627.+--+-- In particular, be careful in using this function if you think your+-- code might interoperate with Javascript.  A na&#xef;ve Javascript+-- library that parses JSON data using @eval@ is vulnerable to attack+-- unless the encoded data represents an object or an array.  JSON+-- implementations in other languages conform to that same restriction+-- to preserve interoperability and security.+value :: Get Value+value = do+  w <- BP.peek+  case w of+    DOUBLE_QUOTE  -> BP.skipN 1 *> (String <$> jstring_)+    OPEN_CURLY    -> BP.skipN 1 *> object_+    OPEN_SQUARE   -> BP.skipN 1 *> array_+    C_f           -> BP.string "false" *> pure (Bool False)+    C_t           -> BP.string "true" *> pure (Bool True)+    C_n           -> BP.string "null" *> pure Null+    _              | w >= 48 && w <= 57 || w == 45+                  -> Number <$> BP.scientific+      | otherwise -> fail "not a valid json value"++-- | Strict version of 'value'. See also 'json''.+value' :: Get Value+value' = do+  w <- BP.peek+  case w of+    DOUBLE_QUOTE  -> do+                     !s <- BP.skipN 1 *> jstring_+                     return (String s)+    OPEN_CURLY    -> BP.skipN 1 *> object_'+    OPEN_SQUARE   -> BP.skipN 1 *> array_'+    C_f           -> BP.string "false" *> pure (Bool False)+    C_t           -> BP.string "true" *> pure (Bool True)+    C_n           -> BP.string "null" *> pure Null+    _              | w >= 48 && w <= 57 || w == 45+                  -> do+                     !n <- BP.scientific+                     return (Number n)+      | otherwise -> fail "not a valid json value"++-- | Parse a quoted JSON string.+jstring :: Get Text+jstring = BP.word8 DOUBLE_QUOTE *> jstring_++-- | Parse a string without a leading quote.+jstring_ :: Get Text+jstring_ = {-# SCC "jstring_" #-} do+  s <- BP.scan False $ \s c -> if s then Just False+                                   else if c == DOUBLE_QUOTE+                                        then Nothing+                                        else Just (c == BACKSLASH)+  BP.word8 DOUBLE_QUOTE+  s1 <- if BACKSLASH `B.elem` s+        then case Z.parse unescape s of+            Right r  -> return r+            Left err -> fail err+         else return s++  case decodeUtf8' s1 of+      Right r  -> return r+      Left err -> fail $ show err++{-# INLINE jstring_ #-}++unescape :: Z.Parser ByteString+unescape = toByteString <$> go mempty where+  go acc = do+    h <- Z.takeWhile (/=BACKSLASH)+    let rest = do+          start <- Z.take 2+          let !slash = B.unsafeHead start+              !t = B.unsafeIndex start 1+              escape = case B.findIndex (==t) "\"\\/ntbrfu" of+                         Just i -> i+                         _      -> 255+          if slash /= BACKSLASH || escape == 255+            then fail "invalid JSON escape sequence"+            else do+            let cont m = go (acc `mappend` byteString h `mappend` m)+                {-# INLINE cont #-}+            if t /= 117 -- 'u'+              then cont (word8 (B.unsafeIndex mapping escape))+              else do+                   a <- hexQuad+                   if a < 0xd800 || a > 0xdfff+                     then cont (charUtf8 (chr a))+                     else do+                       b <- Z.string "\\u" *> hexQuad+                       if a <= 0xdbff && b >= 0xdc00 && b <= 0xdfff+                         then let !c = ((a - 0xd800) `shiftL` 10) ++                                       (b - 0xdc00) + 0x10000+                              in cont (charUtf8 (chr c))+                         else fail "invalid UTF-16 surrogates"+    done <- Z.atEnd+    if done+      then return (acc `mappend` byteString h)+      else rest+  mapping = "\"\\/\n\t\b\r\f"++hexQuad :: Z.Parser Int+hexQuad = do+  s <- Z.take 4+  let hex n | w >= C_0 && w <= C_9 = w - C_0+            | w >= C_a && w <= C_f = w - 87+            | w >= C_A && w <= C_F = w - 55+            | otherwise          = 255+        where w = fromIntegral $ B.unsafeIndex s n+      a = hex 0; b = hex 1; c = hex 2; d = hex 3+  if (a .|. b .|. c .|. d) /= 255+    then return $! d .|. (c `shiftL` 4) .|. (b `shiftL` 8) .|. (a `shiftL` 12)+    else fail "invalid hex escape"++-- $lazy+--+-- The 'json' and 'value' parsers decouple identification from+-- conversion.  Identification occurs immediately (so that an invalid+-- JSON document can be rejected as early as possible), but conversion+-- to a Haskell value is deferred until that value is needed.+--+-- This decoupling can be time-efficient if only a smallish subset of+-- elements in a JSON value need to be inspected, since the cost of+-- conversion is zero for uninspected elements.  The trade off is an+-- increase in memory usage, due to allocation of thunks for values+-- that have not yet been converted.++-- $strict+--+-- The 'json'' and 'value'' parsers combine identification with+-- conversion.  They consume more CPU cycles up front, but have a+-- smaller memory footprint.++-- | Parse a top-level JSON value followed by optional whitespace and+-- end-of-input.  See also: 'json'.+jsonEOF :: Get Value+jsonEOF = json <* BP.skipSpaces++-- | Parse a top-level JSON value followed by optional whitespace and+-- end-of-input.  See also: 'json''.+jsonEOF' :: Get Value+jsonEOF' = json' <* BP.skipSpaces++toByteString :: Builder -> ByteString+toByteString = L.toStrict . toLazyByteString+{-# INLINE toByteString #-}++aeson :: IO [Benchmark]+aeson = do+  path <- pathTo "json-data"+  names <- sort . filter (`notElem` [".", ".."]) <$> getDirectoryContents path+  forM names $ \name -> do+    bs <- B.readFile (path </> name)+    return . bench ("binary-parser/" ++ dropExtension name) $ nf (BP.parseOnly jsonEOF') bs++aesonLazy :: IO [Benchmark]+aesonLazy = do+  path <- pathTo "json-data"+  names <- sort . filter (`notElem` [".", ".."]) <$> getDirectoryContents path+  forM names $ \name -> do+    bs <- L.readFile (path </> name)+    return . bench ("binary-parser/lazy-bytestring/" ++ dropExtension name) $ nf (BP.parseLazy jsonEOF') bs
+ binary-parser-bench/Bench.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE OverloadedStrings #-}++module Main (main) where++import Criterion.Main+import qualified Aeson+import qualified AesonBP+import qualified HttpReq+import Data.List++main :: IO ()+main = do+    http <- HttpReq.headers++    putStrLn "start benchmark http request parser"++    defaultMain http++    putStrLn "start benchmark JSON parser"++    aeson <- Aeson.aeson+    aesonbp <- AesonBP.aeson+    aesonLazy <- Aeson.aesonLazy+    aesonbpLazy <- AesonBP.aesonLazy++    (defaultMain . concat . transpose) [ aeson, aesonbp, aesonLazy, aesonbpLazy ]
+ binary-parser-bench/Common.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Common (+      chunksOf+    , pathTo+    , rechunkBS+    , rechunkT+    ) where++import Control.DeepSeq (NFData(rnf))+import System.Directory (doesDirectoryExist)+import System.FilePath ((</>))+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL++#if !MIN_VERSION_bytestring(0,10,0)+import Data.ByteString.Internal (ByteString(..))++instance NFData ByteString where+    rnf (PS _ _ _) = ()+#endif++chunksOf :: Int -> [a] -> [[a]]+chunksOf k = go+  where go xs = case splitAt k xs of+                  ([],_)  -> []+                  (y, ys) -> y : go ys++rechunkBS :: Int -> B.ByteString -> BL.ByteString+rechunkBS n = BL.fromChunks . map B.pack . chunksOf n . B.unpack++rechunkT :: Int -> T.Text -> TL.Text+rechunkT n = TL.fromChunks . map T.pack . chunksOf n . T.unpack++pathTo :: String -> IO FilePath+pathTo wat = do+  exists <- doesDirectoryExist "bench"+  return $ if exists+           then "bench" </> wat+           else wat
+ binary-parser-bench/HttpReq.hs view
@@ -0,0 +1,134 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}+module HttpReq (headers) where++import Common (pathTo, rechunkBS)+import Control.Applicative+import Criterion.Main (bench, bgroup, nf, nfIO)+import Control.DeepSeq (NFData(..))+import Criterion.Types (Benchmark)+import Network.Wai.Handler.Warp.RequestHeader (parseHeaderLines)+import Data.ByteString.Internal (c2w, w2c)+import qualified Data.Attoparsec.ByteString as AP+import qualified Data.Attoparsec.ByteString.Char8 as APC+import qualified Data.ByteString.Char8 as BC+import qualified Data.Binary.Parser as BP+import Network.HTTP.Types.Version (HttpVersion, http11)+import qualified Scanner as SC++headers :: IO [Benchmark]+headers = do+  req <- BC.readFile =<< pathTo "http-request.txt"+  return [+        bench "http-req/attoparsec" $ nf (AP.parseOnly attoRequest) req+      , bench "http-req/binary-parsers" $ nf (BP.parseOnly bpRequest) req+      , bench "http-req/scanner" $ nf (SC.scanOnly scRequest) req+      , bench "http-req/warp" $ nfIO (parseHeaderLines (BC.lines req))+      ]++--------------------------------------------------------------------------------++instance NFData HttpVersion where+    rnf !_ = ()++attoHeader = do+  name <- APC.takeWhile1 (APC.inClass "a-zA-Z0-9_-") <* APC.char ':' <* APC.skipSpace+  body <- attoBodyLine+  return (name, body)++attoBodyLine = APC.takeTill (\c -> c == '\r' || c == '\n') <* APC.endOfLine++attoReqLine = do+  m <- (APC.takeTill APC.isSpace <* APC.char ' ')+  (p,q) <- BC.break (=='?') <$> (APC.takeTill APC.isSpace <* APC.char ' ')+  v <- attoHttpVersion+  return (m,p,q,v)++attoHttpVersion = http11 <$ APC.string "HTTP/1.1"++attoRequest = (,) <$> (attoReqLine <* APC.endOfLine) <*> attoManyHeader++attoManyHeader = do+  c <- APC.peekChar'+  if c == '\r' || c == '\n'+    then return []++    else (:) <$> attoHeader <*> attoManyHeader++--------------------------------------------------------------------------------++bpHeader = do+  name <- BP.takeWhile1 isHeaderWord8 <* BP.word8 (c2w ':') <* BP.skipSpaces+  body <- bpBodyLine+  return (name, body)+  where+    isHeaderWord8 w = let c = w2c w in+                   ('a' <= c && c <= 'z')+                || ('A' <= c && c <= 'Z')+                || ('0' <= c && c <= '0')+                || c == '_' || c == '-'++bpBodyLine = BP.takeTill (\w -> w == 13 || w == 10) <* BP.endOfLine++bpReqLine = do+  m <- (BP.takeTill BP.isSpace <* BP.word8 (c2w ' '))+  (p,q) <- BC.break (=='?') <$> (BP.takeTill BP.isSpace <* BP.word8 (c2w ' '))+  v <- bpHttpVersion+  return (m,p,q,v)++bpHttpVersion = http11 <$ BP.string "HTTP/1.1"++bpRequest = (,) <$> (bpReqLine <* BP.endOfLine) <*> bpManyHeader++bpManyHeader = do+  w <- BP.peek+  let c = w2c w+  if c == '\r' || c == '\n'+    then return []+    else (:) <$> bpHeader <*> bpManyHeader++--------------------------------------------------------------------------------++scHeader = do+  name <- takeWhile1 (isHeaderChar . w2c) <* SC.char8 ':' <* SC.skipSpace+  body <- scBodyLine+  return (name, body)+  where+    isHeaderChar c = ('a' <= c && c <= 'z')+                  || ('A' <= c && c <= 'Z')+                  || ('0' <= c && c <= '0')+                  || c == '_' || c == '-'++    takeWhile1 p = do+        bs <- SC.takeWhile p+        if BC.null bs then fail "takeWhile1" else return bs++scEndOfLine = do        -- scanner doesn't provide endOfLine, so we roll one here+    w <- SC.anyWord8+    case w of+        10 -> return ()+        13 -> SC.word8 10+        _  -> fail "endOfLine"+{-# INLINE scEndOfLine #-}++scBodyLine = SC.takeWhile (\w -> let c = w2c w in c /= '\r' && c /= '\n') <* scEndOfLine++scReqLine = do+  m <- (SC.takeWhile (not . BP.isSpace) <* SC.char8 ' ')+  (p,q) <- BC.break (=='?') <$> (SC.takeWhile (not . BP.isSpace) <* SC.char8 ' ')+  v <- scHttpVersion+  return (m,p,q,v)++scHttpVersion = http11 <$ SC.string "HTTP/1.1"++scRequest = (,) <$> (scReqLine <* scEndOfLine) <*> scManyHeader++scManyHeader = do+  w <- SC.lookAhead+  case w of+    Just w' -> do+      let c = w2c w'+      if c == '\r' || c == '\n'+        then return []+        else (:) <$> scHeader <*> scManyHeader+    _ -> fail "scManyHeader"
+ binary-parser-bench/Network/Wai/Handler/Warp/ReadInt.hs view
@@ -0,0 +1,66 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE BangPatterns #-}++-- Copyright     : Erik de Castro Lopo <erikd@mega-nerd.com>+-- License       : BSD3++module Network.Wai.Handler.Warp.ReadInt (+    readInt+  , readInt64+  ) where++-- This function lives in its own file because the MagicHash pragma interacts+-- poorly with the CPP pragma.++import Data.ByteString (ByteString)+import qualified Data.ByteString as S+import Data.Int (Int64)+import GHC.Exts (Addr#, Int(..), indexWord8OffAddr#, word2Int#, word8ToWord#)+import GHC.Word++{-# INLINE readInt #-}+readInt :: Integral a => ByteString -> a+readInt bs = fromIntegral $ readInt64 bs++-- This function is used to parse the Content-Length field of HTTP headers and+-- is a performance hot spot. It should only be replaced with something+-- significantly and provably faster.+--+-- It needs to be able work correctly on 32 bit CPUs for file sizes > 2G so we+-- use Int64 here and then make a generic 'readInt' that allows conversion to+-- Int and Integer.++{-# NOINLINE readInt64 #-}+readInt64 :: ByteString -> Int64+readInt64 bs = S.foldl' (\ !i !c -> i * 10 + fromIntegral (mhDigitToInt c)) 0+             $ S.takeWhile isDigit bs++data Table = Table !Addr#++{-# NOINLINE mhDigitToInt #-}+mhDigitToInt :: Word8 -> Int+mhDigitToInt (W8# i) = I# (word2Int# (word8ToWord# (indexWord8OffAddr# addr (word2Int# (word8ToWord# i)))))+  where+    !(Table addr) = table+    table :: Table+    table = Table+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\+        \\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\x00\x00\x00\x00\x00\+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"#++isDigit :: Word8 -> Bool+isDigit w = w >= 48 && w <= 57
+ binary-parser-bench/Network/Wai/Handler/Warp/RequestHeader.hs view
@@ -0,0 +1,172 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE BangPatterns #-}++module Network.Wai.Handler.Warp.RequestHeader (+      parseHeaderLines+    , parseByteRanges+    ) where++import Control.Exception (Exception, throwIO)+import Control.Monad (when)+import Data.Typeable (Typeable)+import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as B (unpack, readInteger)+import Data.ByteString.Internal (ByteString(..), memchr)+import qualified Data.CaseInsensitive as CI+import Data.Word (Word8)+import Foreign.ForeignPtr (withForeignPtr)+import Foreign.Ptr (Ptr, plusPtr, minusPtr, nullPtr)+import Foreign.Storable (peek)+import qualified Network.HTTP.Types as H+-- import Network.Wai.Handler.Warp.Types+import qualified Network.HTTP.Types.Header as HH+-- $setup+-- >>> :set -XOverloadedStrings++data InvalidRequest = NotEnoughLines [String]+                    | BadFirstLine String+                    | NonHttp+                    | IncompleteHeaders+                    | ConnectionClosedByPeer+                    | OverLargeHeader+                    deriving (Eq, Typeable, Show)++instance Exception InvalidRequest++----------------------------------------------------------------++parseHeaderLines :: [ByteString]+                 -> IO (H.Method+                       ,ByteString  --  Path+                       ,ByteString  --  Path, parsed+                       ,ByteString  --  Query+                       ,H.HttpVersion+                       ,H.RequestHeaders+                       )+parseHeaderLines [] = throwIO $ NotEnoughLines []+parseHeaderLines (firstLine:otherLines) = do+    (method, path', query, httpversion) <- parseRequestLine firstLine+    let path = H.extractPath path'+        hdr = map parseHeader otherLines+    return (method, path', path, query, httpversion, hdr)++----------------------------------------------------------------++-- |+--+-- >>> parseRequestLine "GET / HTTP/1.1"+-- ("GET","/","",HTTP/1.1)+-- >>> parseRequestLine "POST /cgi/search.cgi?key=foo HTTP/1.0"+-- ("POST","/cgi/search.cgi","?key=foo",HTTP/1.0)+-- >>> parseRequestLine "GET "+-- *** Exception: Warp: Invalid first line of request: "GET "+-- >>> parseRequestLine "GET /NotHTTP UNKNOWN/1.1"+-- *** Exception: Warp: Request line specified a non-HTTP request+parseRequestLine :: ByteString+                 -> IO (H.Method+                       ,ByteString -- Path+                       ,ByteString -- Query+                       ,H.HttpVersion)+parseRequestLine requestLine@(PS fptr off len) = withForeignPtr fptr $ \ptr -> do+    when (len < 14) $ throwIO baderr+    let methodptr = ptr `plusPtr` off+        limptr = methodptr `plusPtr` len+        lim0 = fromIntegral len++    pathptr0 <- memchr methodptr 32 lim0 -- ' '+    when (pathptr0 == nullPtr || (limptr `minusPtr` pathptr0) < 11) $+        throwIO baderr+    let pathptr = pathptr0 `plusPtr` 1+        lim1 = fromIntegral (limptr `minusPtr` pathptr0)++    httpptr0 <- memchr pathptr 32 lim1 -- ' '+    when (httpptr0 == nullPtr || (limptr `minusPtr` httpptr0) < 9) $+        throwIO baderr+    let httpptr = httpptr0 `plusPtr` 1+        lim2 = fromIntegral (httpptr0 `minusPtr` pathptr)++    checkHTTP httpptr+    !hv <- httpVersion httpptr+    queryptr <- memchr pathptr 63 lim2 -- '?'++    let !method = bs ptr methodptr pathptr0+        !path+          | queryptr == nullPtr = bs ptr pathptr httpptr0+          | otherwise           = bs ptr pathptr queryptr+        !query+          | queryptr == nullPtr = S.empty+          | otherwise           = bs ptr queryptr httpptr0++    return (method,path,query,hv)+  where+    baderr = BadFirstLine $ B.unpack requestLine+    check :: Ptr Word8 -> Int -> Word8 -> IO ()+    check p n w = do+        w0 <- peek $ p `plusPtr` n+        when (w0 /= w) $ throwIO NonHttp+    checkHTTP httpptr = do+        check httpptr 0 72 -- 'H'+        check httpptr 1 84 -- 'T'+        check httpptr 2 84 -- 'T'+        check httpptr 3 80 -- 'P'+        check httpptr 4 47 -- '/'+        check httpptr 6 46 -- '.'+    httpVersion httpptr = do+        major <- peek $ httpptr `plusPtr` 5+        minor <- peek $ httpptr `plusPtr` 7+        return $ if major == (49 :: Word8) && minor == (49 :: Word8) then+            H.http11+          else+            H.http10+    bs ptr p0 p1 = PS fptr o l+      where+        o = p0 `minusPtr` ptr+        l = p1 `minusPtr` p0++----------------------------------------------------------------++-- |+--+-- >>> parseHeader "Content-Length:47"+-- ("Content-Length","47")+-- >>> parseHeader "Accept-Ranges: bytes"+-- ("Accept-Ranges","bytes")+-- >>> parseHeader "Host:  example.com:8080"+-- ("Host","example.com:8080")+-- >>> parseHeader "NoSemiColon"+-- ("NoSemiColon","")++parseHeader :: ByteString -> H.Header+parseHeader s =+    let (k, rest) = S.break (== 58) s -- ':'+        rest' = S.dropWhile (\c -> c == 32 || c == 9) $ S.drop 1 rest+     in (CI.mk k, rest')++parseByteRanges :: S.ByteString -> Maybe HH.ByteRanges+parseByteRanges bs1 = do+    bs2 <- stripPrefix "bytes=" bs1+    (r, bs3) <- range bs2+    ranges (r:) bs3+  where+    range bs2 =+        case stripPrefix "-" bs2 of+            Just bs3 -> do+                (i, bs4) <- B.readInteger bs3+                Just (HH.ByteRangeSuffix i, bs4)+            Nothing -> do+                (i, bs3) <- B.readInteger bs2+                bs4 <- stripPrefix "-" bs3+                case B.readInteger bs4 of+                    Nothing -> Just (HH.ByteRangeFrom i, bs4)+                    Just (j, bs5) -> Just (HH.ByteRangeFromTo i j, bs5)+    ranges front bs3 =+        case stripPrefix "," bs3 of+            Nothing -> Just (front [])+            Just bs4 -> do+                (r, bs5) <- range bs4+                ranges (front . (r:)) bs5++    stripPrefix x y+        | x `S.isPrefixOf` y = Just (S.drop (S.length x) y)+        | otherwise = Nothing
+ mozillaCAStore.pem view
@@ -0,0 +1,3955 @@+##+## Bundle of CA Root Certificates+##+## Certificate data from Mozilla as of: Wed Jun  7 03:12:05 2017 GMT+##+## This is a bundle of X.509 certificates of public Certificate Authorities+## (CA). These were automatically extracted from Mozilla's root certificates+## file (certdata.txt).  This file can be found in the mozilla source tree:+## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt+##+## It contains the certificates in PEM format and therefore+## can be directly used with curl / libcurl / php_curl, or with+## an Apache+mod_ssl webserver for SSL client authentication.+## Just configure this file as the SSLCACertificateFile.+##+## Conversion done with mk-ca-bundle.pl version 1.27.+## SHA256: 93753268e1c596aee21893fb1c6975338389132f15c942ed65fc394a904371d7+##+++GlobalSign Root CA+==================+-----BEGIN CERTIFICATE-----+MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx+GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds+b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV+BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD+VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa+DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc+THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb+Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP+c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX+gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF+AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj+Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG+j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH+hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC+X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==+-----END CERTIFICATE-----++GlobalSign Root CA - R2+=======================+-----BEGIN CERTIFICATE-----+MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv+YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh+bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT+aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln+bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6+ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp+s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN+S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL+TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C+ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E+FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i+YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN+BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp+9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu+01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7+9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7+TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==+-----END CERTIFICATE-----++Verisign Class 3 Public Primary Certification Authority - G3+============================================================+-----BEGIN CERTIFICATE-----+MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv+cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl+IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh+dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy+dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv+cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC+ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1+EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc+cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw+EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj+055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA+ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f+j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC+/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0+xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa+t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==+-----END CERTIFICATE-----++Entrust.net Premium 2048 Secure Server CA+=========================================+-----BEGIN CERTIFICATE-----+MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u+ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp+bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV+BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx+NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3+d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl+MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u+ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A+MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL+Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr+hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW+nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi+VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ+KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy+T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf+zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT+J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e+nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE=+-----END CERTIFICATE-----++Baltimore CyberTrust Root+=========================+-----BEGIN CERTIFICATE-----+MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE+ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li+ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC+SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs+dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME+uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB+UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C+G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9+XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr+l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI+VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB+BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh+cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5+hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa+Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H+RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp+-----END CERTIFICATE-----++AddTrust Low-Value Services Root+================================+-----BEGIN CERTIFICATE-----+MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRU+cnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQsw+CQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBO+ZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEB+AQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ulCDtbKRY6+54eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWr+oulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyldI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1+Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJui+GMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8w+HQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTAD+AQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJT+RTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEw+HwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxt+ZBsfzQ3duQH6lmM0MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0Ph+iVYrqW9yTkkz43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY+eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xr+mYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj+ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=+-----END CERTIFICATE-----++AddTrust External Root+======================+-----BEGIN CERTIFICATE-----+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML+QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD+VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw+NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU+cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg+Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821++iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw+Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo+aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy+2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7+7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL+VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk+VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB+IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl+j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355+e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u+G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=+-----END CERTIFICATE-----++AddTrust Public Services Root+=============================+-----BEGIN CERTIFICATE-----+MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UEChML+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQDExdBZGRU+cnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJ+BgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5l+dHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEF+AAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV6tsfSlbu+nyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nXGCwwfQ56HmIexkvA/X1i+d9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnPdzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSG+Aa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAw+HM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0G+A1UdDgQWBBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB+/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0Ux+FDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4G+A1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4+JNojVhaTdt02KLmuG7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL++YPoRNWyQSW/iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao+GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9+Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H+EufOX1362KqxMy3ZdvJOOjMMK7MtkAY=+-----END CERTIFICATE-----++AddTrust Qualified Certificates Root+====================================+-----BEGIN CERTIFICATE-----+MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UEChML+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQDExpBZGRU+cnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcx+CzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQ+IE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwqxBb/4Oxx+64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G87B4pfYOQnrjfxvM0PC3+KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i2O+tCBGaKZnhqkRFmhJePp1tUvznoD1o+L/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GR+wVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HU+MIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/+BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UE+BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29y+azEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQAD+ggEBABmrder4i2VhlRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxG+GuoYQ992zPlmhpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X+dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9ze+RXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB+iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE=+-----END CERTIFICATE-----++Entrust Root Certification Authority+====================================+-----BEGIN CERTIFICATE-----+MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV+BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw+b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG+A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0+MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu+MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu+Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v+dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB+ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz+A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww+Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68+j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN+rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw+DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1+MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH+hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA+A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM+Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa+v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS+W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0+tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8+-----END CERTIFICATE-----++GeoTrust Global CA+==================+-----BEGIN CERTIFICATE-----+MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQK+Ew1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIwNTIxMDQw+MDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j+LjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB+CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjo+BbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDviS2Aelet+8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU1XupGc1V3sjs0l44U+Vc+T4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagU+vTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTAD+AQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVk+DBF9qn1luMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57Q+zxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4+d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2+mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6p+XE0zX5IJL4hmXXeXxx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvm+Mw==+-----END CERTIFICATE-----++GeoTrust Global CA 2+====================+-----BEGIN CERTIFICATE-----+MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN+R2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQwMzA0MDUw+MDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j+LjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw+ggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/+NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hLTytCOb1k+LUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL5mkWRxHCJ1kDs6ZgwiFA+Vvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7S4wMcoKK+xfNAGw6EzywhIdLFnopsk/b+HdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQF+MAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNH+K266ZUapEBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7+srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6Yvh+ZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqL+OzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkC+x1YAzUm5s2x7UwQa4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqF+H4z1Ir+rzoPz4iIprn2DQKi6bA==+-----END CERTIFICATE-----++GeoTrust Universal CA+=====================+-----BEGIN CERTIFICATE-----+MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN+R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0MDMwNDA1+MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu+Yy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP+ADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9t+JPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTTQjOgNB0e+RXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFhF7em6fgemdtzbvQKoiFs+7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2vc7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d+8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7V+qnJNk22CDtucvc+081xdVHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3Cga+Rr0BHdCXteGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hB+Z3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZu+KGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08+ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0+XG0D08DYj3rWMB8GA1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIB+hjANBgkqhkiG9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc+aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2+qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL+oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsK+xr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxF+KyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2+DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9ER/frslK+xfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQtDF4JbAiXfKM9fJP/P6EU+p8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6ZKy7PtXny3YuxadIwVyQD8vI+P/rmMuGNG2+k5o7Y+SlIis5z/iw=+-----END CERTIFICATE-----++GeoTrust Universal CA 2+=======================+-----BEGIN CERTIFICATE-----+MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN+R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcNMDQwMzA0+MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3Qg+SW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUA+A4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0+DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUGFF+3Qs17+j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdqXbboW0W63MOhBW9Wjo8Q+JqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxLse4YuU6W3Nx2/zu+z18DwPw76L5GG//a+QMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2+WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP+20gaXT73y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAn+ZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgC+SqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG+8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2++/CfXGJx7Tz0RzgQKzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8E+BAMCAYYwDQYJKoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z+dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ+4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr++mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpq+A1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpg+Y+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiP+pm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJVOCiNUW7d+FGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH6aLcr34YEoP9VhdBLtUp+gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm+X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS+-----END CERTIFICATE-----++Visa eCommerce Root+===================+-----BEGIN CERTIFICATE-----+MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQG+EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug+QXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2+WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMm+VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv+bW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h2mCxlCfL+F9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4ElpF7sDPwsRROEW+1QK8b+RaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdVZqW1LS7YgFmypw23RuwhY/81q6UCzyr0+TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI+/k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzs+GHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG+MB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxc+CLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUW+YFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pz+zkWKsKZJ/0x9nXGIxHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBu+YQa7FkKMcPcw++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt+398znM/jra6O1I7mT1GvFpLgXPYHDw==+-----END CERTIFICATE-----++Certum Root CA+==============+-----BEGIN CERTIFICATE-----+MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK+ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla+Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u+by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x+wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL+kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ+89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K+Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P+NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq+hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze++GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg+GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/+0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS+qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw==+-----END CERTIFICATE-----++Comodo AAA Services root+========================+-----BEGIN CERTIFICATE-----+MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS+R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg+TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw+MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl+c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV+BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC+ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG+C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs+i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW+Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH+Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK+Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f+BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl+cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz+LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm+7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz+Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z+8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C+12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==+-----END CERTIFICATE-----++Comodo Secure Services root+===========================+-----BEGIN CERTIFICATE-----+MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS+R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg+TGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAw+MDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu+Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAi+BgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP+ADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPMcm3ye5drswfxdySRXyWP+9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3SHpR7LZQdqnXXs5jLrLxkU0C8j6ysNstc+rbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rC+oznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3V+p6ea5EQz6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4E+FgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w+gYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmlj+YXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlm+aWNhdGVTZXJ2aWNlcy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm+4J4oqF7Tt/Q05qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj+Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRL+DXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw+pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1H+RR3B7Hzs/Sk=+-----END CERTIFICATE-----++Comodo Trusted Services root+============================+-----BEGIN CERTIFICATE-----+MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS+R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg+TGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEw+MDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1h+bmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUw+IwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOC+AQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWWfnJSoBVC21ndZHoa0Lh7+3TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMtTGo87IvDktJTdyR0nAducPy9C1t2ul/y+/9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6+juljatEPmsbS9Is6FARW1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsS+ivnkBbA7kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1Ud+DgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB+/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRp+ZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENl+cnRpZmljYXRlU2VydmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8Ntw+uleGFTQQuS9/HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32+pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDA+BHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l+R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O+9y5Xt5hwXsjEeLBi+-----END CERTIFICATE-----++QuoVadis Root CA+================+-----BEGIN CERTIFICATE-----+MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcGA1UE+ChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0+eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAz+MTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRp+cyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQD+EyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF+AAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Ypli4kVEAkOPcahdxYTMuk+J0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2DrOpm2RgbaIr1VxqYuvXtdj182d6UajtL+F8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeL+YzcS19Dsw3sgQUSj7cugF+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWen+AScOospUxbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4w+PQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9y+ZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7+MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmlj+YXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs+ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh+Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEW+Fmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy+/fMIGu+BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkw+FwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0+aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6+tlCLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSkfnIYj9lo+fFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf87C9TqnN7Az10buYWnuul+LsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2x+gI4JVrmcGmD+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi+5upZIof4l/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi+5nrQNiOKSnQ2+Q==+-----END CERTIFICATE-----++QuoVadis Root CA 2+==================+-----BEGIN CERTIFICATE-----+MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT+EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx+ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM+aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC+DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6+XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk+lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB+lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy+lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt+66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn+wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh+D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy+BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie+J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud+DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU+a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT+ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv+Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3+UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm+VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW+IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1+WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X+f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II+4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8+VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u+-----END CERTIFICATE-----++QuoVadis Root CA 3+==================+-----BEGIN CERTIFICATE-----+MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT+EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx+OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM+aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC+DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg+DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij+KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K+DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv+BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp+p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8+nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX+MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM+Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz+uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT+BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj+YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0+aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB+BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD+VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4+ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE+AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV+qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s+hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z+POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2+Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp+8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC+bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu+g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p+vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr+qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto=+-----END CERTIFICATE-----++Security Communication Root CA+==============================+-----BEGIN CERTIFICATE-----+MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP+U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw+HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP+U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw+8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM+DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX+5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd+DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2+JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw+DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g+0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a+mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ+s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ+6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi+FL39vmwLAw==+-----END CERTIFICATE-----++Sonera Class 2 Root CA+======================+-----BEGIN CERTIFICATE-----+MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG+U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoXDTIxMDQw+NjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh+IENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3+/Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybT+dXnt5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s3TmVToMG+f+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2EjvOr7nQKV0ba5cTppCD8P+tOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu8nYybieDwnPz3BjotJPqdURrBGAgcVeH+nfO+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITT+XjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt+0jSv9zilzqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEI+cbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavph+Oe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSx+EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLH+llpwrN9M+-----END CERTIFICATE-----++UTN USERFirst Hardware Root CA+==============================+-----BEGIN CERTIFICATE-----+MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UE+BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl+IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAd+BgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgx+OTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0+eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz+ZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwggEiMA0GCSqGSIb3+DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlI+wrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFd+tqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8+i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjf+Pe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkw+gbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WF+lp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNF+UkZpcnN0LUhhcmR3YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUF+BwMGBggrBgEFBQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM+//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogW+XecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2+lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kn+iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67+nfhmqA==+-----END CERTIFICATE-----++Camerfirma Chambers of Commerce Root+====================================+-----BEGIN CERTIFICATE-----+MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe+QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i+ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx+NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp+cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn+MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC+AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU+xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH+NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW+DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV+d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud+EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v+cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P+AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh+bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD+VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz+aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi+fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD+L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN+UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n+ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1+erfutGWaIZDgqtCYvDi1czyL+Nw=+-----END CERTIFICATE-----++Camerfirma Global Chambersign Root+==================================+-----BEGIN CERTIFICATE-----+MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe+QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i+ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx+NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt+YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg+MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw+ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J+1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O+by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl+6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c+8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/+BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j+aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B+Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj+aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y+ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh+bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA+PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y+gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ+PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4+IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes+t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==+-----END CERTIFICATE-----++XRamp Global CA Root+====================+-----BEGIN CERTIFICATE-----+MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE+BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj+dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB+dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx+HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg+U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp+dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu+IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx+foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE+zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs+AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry+xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud+EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap+oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC+AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc+/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt+qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n+nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz+8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw=+-----END CERTIFICATE-----++Go Daddy Class 2 CA+===================+-----BEGIN CERTIFICATE-----+MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY+VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp+ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG+A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g+RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD+ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv+2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32+qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j+YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY+vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O+BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o+atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu+MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim+PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt+I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ+HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI+Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b+vZ8=+-----END CERTIFICATE-----++Starfield Class 2 CA+====================+-----BEGIN CERTIFICATE-----+MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc+U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo+MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG+A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG+SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY+bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ+JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm+epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN+F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF+MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f+hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo+bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g+QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs+afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM+PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl+xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD+KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3+QBFGmh95DmK/D5fs4C8fF5Q=+-----END CERTIFICATE-----++StartCom Certification Authority+================================+-----BEGIN CERTIFICATE-----+MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN+U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu+ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0+NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk+LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg+U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw+ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y+o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/+Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d+eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt+2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z+6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ+osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/+untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc+UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT+37uMdBNSSwIDAQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE+FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0+Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj+YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUH+AgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRw+Oi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYg+U3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlhYmlsaXR5+LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2YgdGhlIFN0YXJ0Q29tIENl+cnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUgYXQgaHR0cDovL2NlcnQuc3Rh+cnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilT+dGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOC+AgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8jhvh+3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXm+vB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHk+fY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3+fsNrarnDy0RLrHiQi+fHLB5LEUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZ+EoalHmdkrQYuL6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq+yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl+1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/+lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38coro+g14=+-----END CERTIFICATE-----++Taiwan GRCA+===========+-----BEGIN CERTIFICATE-----+MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG+EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X+DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv+dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD+ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN+w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5+BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O+1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO+htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov+J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7+Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t+B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB+O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8+lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV+HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2+09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ+TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj+Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2+Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU+D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz+DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk+Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk+7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ+CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy++fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS+-----END CERTIFICATE-----++Swisscom Root CA 1+==================+-----BEGIN CERTIFICATE-----+MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQG+EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy+dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4+MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln+aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIIC+IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9m2BtRsiM+MW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdihFvkcxC7mlSpnzNApbjyF+NDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/TilftKaNXXsLmREDA/7n29uj/x2lzZAe+AR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkC+b6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn+7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbN+cA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUp+WyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5+haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNY+MUJDLXT5xp6mig/p/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw+HQYDVR0hBBYwFDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j+BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9+MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn+jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQ+MbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4H+VtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtl+vrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ipmXeascCl+OS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HHb6D0jqTsNFFbjCYDcKF3+1QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBaZmBwXarNeNQk7shBoJMBkpxq+nvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCy+x/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMW+NY6E0F/6MBr1mmz0DlP5OlvRHA==+-----END CERTIFICATE-----++DigiCert Assured ID Root CA+===========================+-----BEGIN CERTIFICATE-----+MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw+IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx+MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL+ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO+9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy+UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW+/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy+oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf+GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF+66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq+hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc+EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn+SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i+8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==+-----END CERTIFICATE-----++DigiCert Global Root CA+=======================+-----BEGIN CERTIFICATE-----+MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw+HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw+MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3+dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn+TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5+BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H+4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y+7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB+o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm+8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF+BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr+EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt+tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886+UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk+CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=+-----END CERTIFICATE-----++DigiCert High Assurance EV Root CA+==================================+-----BEGIN CERTIFICATE-----+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw+KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw+MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ+MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu+Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t+Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS+OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3+MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ+NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe+h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB+Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY+JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ+V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp+myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK+mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K+-----END CERTIFICATE-----++Certplus Class 2 Primary CA+===========================+-----BEGIN CERTIFICATE-----+MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkGA1UE+BhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkgQ0EwHhcN+OTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2Vy+dHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP+ADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR+5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyLkcAbmXuZ+Vg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCdEgETjdyAYveVqUSISnFO+YFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yasH7WLO7dDWWuwJKZtkIvEcupdM5i3y95e+e++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRME+CDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJ+YIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29t+L0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvD+P9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1R+TtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/++7UCmnYR0ObncHoUW2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW+//1IMwrh3KWBkJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7+l7+ijrRU+-----END CERTIFICATE-----++DST Root CA X3+==============+-----BEGIN CERTIFICATE-----+MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK+ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X+DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1+cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD+ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT+rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9+UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy+xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d+utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T+AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ+MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug+dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE+GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw+RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS+fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ+-----END CERTIFICATE-----++DST ACES CA X6+==============+-----BEGIN CERTIFICATE-----+MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQG+EwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERTVCBBQ0VT+MRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzExMjAyMTE5NTha+MFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UE+CxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOC+AQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPuktKe1jzI+DZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7gLFViYsx+tC3dr5BPTCa+pCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZHfAjIgrrep4c9oW24MFbCswKBXy314pow+GCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPy+MjwmR/onJALJfh1biEITajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1Ud+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3Qu+Y29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRy+dXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQU+CXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V2+5FYrnJmQ6AgwbN99Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6t+Fr8hlxCBPeP/h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq+nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXs+vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3+oKfN5XozNmr6mis=+-----END CERTIFICATE-----++SwissSign Gold CA - G2+======================+-----BEGIN CERTIFICATE-----+MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw+EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN+MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp+c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B+AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq+t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C+jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg+vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF+ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR+AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend+jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO+peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR+7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi+GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw+AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64+OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov+L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm+5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr+44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf+Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m+Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp+mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk+vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf+KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br+NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj+viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ+-----END CERTIFICATE-----++SwissSign Silver CA - G2+========================+-----BEGIN CERTIFICATE-----+MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT+BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X+DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3+aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG+9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644+N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm++/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH+6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu+MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h+qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5+FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs+ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc+celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X+CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/+BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB+tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0+cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P+4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F+kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L+3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx+/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa+DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP+e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu+WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ+DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub+DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u+-----END CERTIFICATE-----++GeoTrust Primary Certification Authority+========================================+-----BEGIN CERTIFICATE-----+MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQG+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJpbWFyeSBD+ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgx+CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQ+cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB+CgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9AWbK7hWN+b6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjAZIVcFU2Ix7e64HXprQU9+nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE07e9GceBrAqg1cmuXm2bgyxx5X9gaBGge+RwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGt+tm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD+AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZI+hvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5K+Ts4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFN+NWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHa+Floxt/m0cYASSJlyc1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG+1riR/aYNKxoUAT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=+-----END CERTIFICATE-----++thawte Primary Root CA+======================+-----BEGIN CERTIFICATE-----+MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UE+BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2+aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv+cml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3+MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwg+SW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMv+KGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMT+FnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs+oPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ+1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGc+q/gcfomk6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/K+aAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4p+afs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD+VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUF+AAOCAQEAeRHAS7ORtvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeE+uzLlQRHAd9mzYJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX+xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89+jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH+z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA==+-----END CERTIFICATE-----++VeriSign Class 3 Public Primary Certification Authority - G5+============================================================+-----BEGIN CERTIFICATE-----+MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE+BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO+ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk+IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp+ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln+biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh+dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw+ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz+j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD+Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/+Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r+fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/+BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv+Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy+aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG+SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl++X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE+KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC+Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE+ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq+-----END CERTIFICATE-----++SecureTrust CA+==============+-----BEGIN CERTIFICATE-----+MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG+EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy+dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe+BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX+OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t+DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH+GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b+01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH+ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/+BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj+aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ+KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu+SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf+mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ+nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR+3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=+-----END CERTIFICATE-----++Secure Global CA+================+-----BEGIN CERTIFICATE-----+MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG+EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH+bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg+MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg+Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx+YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ+bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g+8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV+HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi+0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud+EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn+oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA+MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6++OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn+CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5+3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc+f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW+-----END CERTIFICATE-----++COMODO Certification Authority+==============================+-----BEGIN CERTIFICATE-----+MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE+BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG+A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1+dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb+MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD+T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww+xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV+4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA+1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI+rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k+b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC+AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP+OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/+RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc+IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ==+-----END CERTIFICATE-----++Network Solutions Certificate Authority+=======================================+-----BEGIN CERTIFICATE-----+MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG+EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr+IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx+MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu+MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx+jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT+aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT+crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc+/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB+AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP+BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv+bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA+A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q+4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/+GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv+wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD+ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey+-----END CERTIFICATE-----++COMODO ECC Certification Authority+==================================+-----BEGIN CERTIFICATE-----+MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC+R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE+ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB+dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix+GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR+Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo+b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X+4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni+wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG+FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA+U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=+-----END CERTIFICATE-----++Security Communication EV RootCA1+=================================+-----BEGIN CERTIFICATE-----+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc+U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh+dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE+BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl+Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC+AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO+/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX+WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z+ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4+bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK+9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG+SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm+iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG+Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW+mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW+T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490+-----END CERTIFICATE-----++OISTE WISeKey Global Root GA CA+===============================+-----BEGIN CERTIFICATE-----+MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE+BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG+A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH+bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD+VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw+IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5+IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9+Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg+Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD+d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ+/yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R+LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw+AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ+KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm+MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4++vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa+hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY+okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0=+-----END CERTIFICATE-----++Certigna+========+-----BEGIN CERTIFICATE-----+MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw+EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3+MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI+Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q+XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH+GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p+ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg+DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf+Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ+tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ+BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J+SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA+hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs++ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu+PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY+1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw+WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==+-----END CERTIFICATE-----++Deutsche Telekom Root CA 2+==========================+-----BEGIN CERTIFICATE-----+MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMT+RGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEG+A1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5+MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0G+A1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBS+b290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEUha88EOQ5+bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhCQN/Po7qCWWqSG6wcmtoI+KyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1MjwrrFDa1sPeg5TKqAyZMg4ISFZbavva4VhY+AUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aK+Se5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTV+jlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNV+HRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynr+E/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSy+zhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8+rZ7/gFnkm0W09juwzTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4G+dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU+Cm26OWMohpLzGITY+9HPBVZkVw==+-----END CERTIFICATE-----++Cybertrust Global Root+======================+-----BEGIN CERTIFICATE-----+MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li+ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4+MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD+ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA++Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW+0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL+AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin+89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT+8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2+MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G+A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO+lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi+5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2+hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T+X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW+WL1WMRJOEcgh4LMRkWXbtKaIOM5V+-----END CERTIFICATE-----++ePKI Root Certification Authority+=================================+-----BEGIN CERTIFICATE-----+MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG+EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg+Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx+MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq+MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B+AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs+IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi+lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv+qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX+12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O+WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1++ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao+lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/+vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi+Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi+MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH+ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0+1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq+KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV+xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP+NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r+GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE+xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx+gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy+sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD+BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw=+-----END CERTIFICATE-----++T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3+=============================================================================================================================+-----BEGIN CERTIFICATE-----+MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRSMRgwFgYDVQQH+DA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJpbGltc2VsIHZlIFRla25vbG9q+aWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSwVEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ry+b25payB2ZSBLcmlwdG9sb2ppIEFyYcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNV+BAsMGkthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUg+S8O2ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAeFw0wNzA4+MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIxGDAWBgNVBAcMD0dlYnpl+IC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmlsaW1zZWwgdmUgVGVrbm9sb2ppayBBcmHF+n3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBUQUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZl+IEtyaXB0b2xvamkgQXJhxZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2Ft+dSBTZXJ0aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7ZrIFNl+cnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIBIjANBgkqhkiG9w0B+AQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4hgb46ezzb8R1Sf1n68yJMlaCQvEhO+Eav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yKO7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1+xnnRFDDtG1hba+818qEhTsXOfJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR+6Oqeyjh1jmKwlZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL+hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQIDAQABo0IwQDAd+BgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF+MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmPNOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4+N5EY3ATIZJkrGG2AA1nJrvhY0D7twyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLT+y9LQQfMmNkqblWwM7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYh+LBOhgLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5noN+J1q2M+dqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUsyZyQ2uypQjyttgI=+-----END CERTIFICATE-----++certSIGN ROOT CA+================+-----BEGIN CERTIFICATE-----+MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD+VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa+Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE+CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I+JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH+rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2+ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD+0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943+AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B+Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB+AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8+SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0+x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt+vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz+TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD+-----END CERTIFICATE-----++CNNIC ROOT+==========+-----BEGIN CERTIFICATE-----+MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJDTjEOMAwGA1UE+ChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2MDcwOTE0WhcNMjcwNDE2MDcw+OTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1Qw+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzD+o+/hn7E7SIX1mlwhIhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tiz+VHa6dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZOV/kbZKKT+VrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrCGHn2emU1z5DrvTOTn1Or+czvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gNv7Sg2Ca+I19zN38m5pIEo3/PIKe38zrK+y5nLAgMBAAGjczBxMBEGCWCGSAGG+EIBAQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscC+wQ7vptU7ETAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991S+lgrHAsEO76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnKOOK5+Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvHugDnuL8BV8F3RTIM+O/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7HgviyJA/qIYM/PmLXoXLT1tLYhFHxUV8+BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fLbuXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2+G8kS1sHNzYDzAgE8yGnLRUhj2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5m+mxE=+-----END CERTIFICATE-----++GeoTrust Primary Certification Authority - G3+=============================================+-----BEGIN CERTIFICATE-----+MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UE+BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA4IEdlb1RydXN0+IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFy+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIz+NTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAo+YykgMjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMT+LUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZI+hvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5j+K/BGvESyiaHAKAxJcCGVn2TAppMSAmUmhsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdE+c5IiaacDiGydY8hS2pgn5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3C+IShwiP/WJmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exALDmKu+dlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZChuOl1UcCAwEAAaNC+MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMR5yo6hTgMdHNxr+2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IBAQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9+cr5HqQ6XErhK8WTTOd8lNNTBzU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbE+Ap7aDHdlDkQNkv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD+AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUHSJsMC8tJP33s+t/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2Gspki4cErx5z481+oghLrGREt+-----END CERTIFICATE-----++thawte Primary Root CA - G2+===========================+-----BEGIN CERTIFICATE-----+MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDELMAkGA1UEBhMC+VVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMpIDIwMDcgdGhhd3RlLCBJbmMu+IC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3Qg+Q0EgLSBHMjAeFw0wNzExMDUwMDAwMDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEV+MBMGA1UEChMMdGhhd3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBG+b3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAt+IEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/BebfowJPDQfGAFG6DAJS+LSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6papu+7qzcMBniKI11KOasf2twu8x+qi5+8/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU+mtgAMADna3+FGO6Lts6KDPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUN+G4k8VIZ3KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41oxXZ3K+rr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==+-----END CERTIFICATE-----++thawte Primary Root CA - G3+===========================+-----BEGIN CERTIFICATE-----+MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UE+BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2+aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv+cml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0w+ODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh+d3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYD+VQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIG+A1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A+MIIBCgKCAQEAsr8nLPvb2FvdeHsbnndmgcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2At+P0LMqmsywCPLLEHd5N/8YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC++BsUa0Lfb1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS99irY+7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2SzhkGcuYMXDhpxwTW+vGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUkOQIDAQABo0IwQDAPBgNVHRMBAf8E+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJ+KoZIhvcNAQELBQADggEBABpA2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweK+A3rD6z8KLFIWoCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu+t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7cKUGRIjxpp7sC+8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fMm7v/OeZWYdMKp8RcTGB7BXcm+er/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZuMdRAGmI0Nj81Aa6sY6A=+-----END CERTIFICATE-----++GeoTrust Primary Certification Authority - G2+=============================================+-----BEGIN CERTIFICATE-----+MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC+VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu+Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD+ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1+OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg+MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl+b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG+BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc+KiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD+VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q++EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m+ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2+npaqBA+K+-----END CERTIFICATE-----++VeriSign Universal Root Certification Authority+===============================================+-----BEGIN CERTIFICATE-----+MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE+BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO+ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk+IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u+IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv+cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl+IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0+aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj+1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGP+MiJhgsWHH26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72+9fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I+AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycR+tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G+CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2O+a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud+DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3+Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx+Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx+P/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P+wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4+mJO37M2CYfE45k+XmCpajQ==+-----END CERTIFICATE-----++VeriSign Class 3 Public Primary Certification Authority - G4+============================================================+-----BEGIN CERTIFICATE-----+MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjELMAkGA1UEBhMC+VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3+b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVz+ZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmlj+YXRpb24gQXV0aG9yaXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjEL+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU+cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRo+b3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8+Utpkmw4tXNherJI9/gHmGUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGz+rl0Bp3vefLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUwAwEB+/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEw+HzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24u+Y29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMWkf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMD+A2gAMGUCMGYhDBgmYFo4e1ZC4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIx+AJw9SDkjOVgaFRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==+-----END CERTIFICATE-----++NetLock Arany (Class Gold) Főtanúsítvány+========================================+-----BEGIN CERTIFICATE-----+MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G+A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610+dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB+cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx+MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO+ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv+biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6+c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu+0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw+/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk+H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw+fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1+neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB+BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW+qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta+YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC+bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna+NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu+dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=+-----END CERTIFICATE-----++Staat der Nederlanden Root CA - G2+==================================+-----BEGIN CERTIFICATE-----+MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE+CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g+Um9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMC+TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l+ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ+5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8SpuOUfiUtn+vWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPUZ5uW6M7XxgpT0GtJlvOj+CwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvEpMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiil+e7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCR+OME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpI+CT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V65+48r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIi+trzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737+qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMB+AAGjgZcwgZQwDwYDVR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcC+ARYxaHR0cDovL3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUA+A4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz++51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwj+f/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaN+kqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfk+CpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxLvJxxcypF+URmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkmbEgeqmiSBeGCc1qb3Adb+CG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8sV4pAWja63XVECDdCcAz+3F4h+oKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoV+IPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm+66+KAQ==+-----END CERTIFICATE-----++Hongkong Post Root CA 1+=======================+-----BEGIN CERTIFICATE-----+MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT+DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx+NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n+IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF+AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1+ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr+auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh+qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY+V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV+HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i+h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio+l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei+IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps+T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT+c4afU9hDDl3WY4JxHYB0yvbiAmvZWg==+-----END CERTIFICATE-----++SecureSign RootCA11+===================+-----BEGIN CERTIFICATE-----+MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi+SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS+b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw+KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1+cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL+TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO+wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq+g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP+O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA+bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX+t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh+OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r+bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ+Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01+y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061+lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I=+-----END CERTIFICATE-----++ACEDICOM Root+=============+-----BEGIN CERTIFICATE-----+MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UEAwwNQUNFRElD+T00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMB4XDTA4+MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEWMBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoG+A1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEF+AAOCAg8AMIICCgKCAgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHk+WLn709gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7XBZXehuD+YAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5PGrjm6gSSrj0RuVFCPYew+MYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAKt0SdE3QrwqXrIhWYENiLxQSfHY9g5QYb+m8+5eaA9oiM/Qj9r+hwDezCNzmzAv+YbX79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbk+HQl/Sog4P75n/TSW9R28MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTT+xKJxqvQUfecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI2Sf2+3EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyHK9caUPgn6C9D4zq9+2Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEaeZAwUswdbxcJzbPEHXEUkFDWug/Fq+TYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz+4SsrSbbXc6GqlPUB53NlTKxQMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU+9QHnc2VMrFAwRAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv+bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWImfQwng4/F9tqg+aHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3gvoFNTPhNahXwOf9jU8/kzJP+eGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKeI6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1Pwk+zQSulgUV1qzOMPPKC8W64iLgpq0i5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1+ThCojz2GuHURwCRiipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oI+KiMnMCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZo5NjEFIq+nxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6zqylfDJKZ0DcMDQj3dcE+I2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacNGHk0vFQYXlPKNFHtRQrmjseCNj6nOGOp+MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o+tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA==+-----END CERTIFICATE-----++Microsec e-Szigno Root CA 2009+==============================+-----BEGIN CERTIFICATE-----+MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER+MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv+c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o+dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE+BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt+U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA+fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG+0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA+pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm+1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC+AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf+QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE+FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o+lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX+I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775+tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02+yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi+LXpUq3DDfSJlgnCW+-----END CERTIFICATE-----++GlobalSign Root CA - R3+=======================+-----BEGIN CERTIFICATE-----+MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv+YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh+bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT+aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln+bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt+iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ+0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3+rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl+OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2+xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE+FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7+lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8+EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E+bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18+YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r+kpeDMdmztcpHWD9f+-----END CERTIFICATE-----++Autoridad de Certificacion Firmaprofesional CIF A62634068+=========================================================+-----BEGIN CERTIFICATE-----+MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA+BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2+MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw+QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB+NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD+Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P+B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY+7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH+ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI+plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX+MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX+LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK+bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU+vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud+EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH+DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp+cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA+bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx+ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx+51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk+R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP+T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f+Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl+osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR+crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR+saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD+KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi+6Et8Vcad+qMUu2WFbm5PEn4KPJ2V+-----END CERTIFICATE-----++Izenpe.com+==========+-----BEGIN CERTIFICATE-----+MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG+EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz+MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu+QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ+03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK+ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC+PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT+OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK+F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK+0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij++0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB+leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID+AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw++SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG+NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx+MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O+BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l+Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga+kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q+hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs+g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5+aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5+nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC+ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo+Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z+WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==+-----END CERTIFICATE-----++Chambers of Commerce Root - 2008+================================+-----BEGIN CERTIFICATE-----+MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD+MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv+bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu+QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy+Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl+ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF+EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl+cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC+AQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA+XuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj+h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/+ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk+NNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g+D2vlGts/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331+lubKgdaX8ZSD6e2wsWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ+0wlf2eOKNcx5Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj+ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2+EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI+G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ+BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh+bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh+bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC+CQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH+AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1+wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH+3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU+RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6+M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1+YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF+9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK+zBIKinmwPQN/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG+nrDQWzilm1DefhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg+OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ+-----END CERTIFICATE-----++Global Chambersign Root - 2008+==============================+-----BEGIN CERTIFICATE-----+MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD+MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv+bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu+QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx+NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg+Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ+QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD+aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf+VtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf+XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0+ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB+/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA+TH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M+H/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe+Ox2YItaswTXbo6Al/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF+HTynyQbehP9r6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh+wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB+AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT+BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE+BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm+aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm+aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp+1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0+dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG+/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6+ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s+dZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg+9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH+foUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du+qqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr+P3iZ8ntxPjzxmKfFGBI/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq+c5iJWzouE4gev8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z+09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B+-----END CERTIFICATE-----++Go Daddy Root Certificate Authority - G2+========================================+-----BEGIN CERTIFICATE-----+MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT+B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu+MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5+MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6+b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G+A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI+hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq+9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd+fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl+NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC+MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9+BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac+vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r+5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV+N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO+LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1+-----END CERTIFICATE-----++Starfield Root Certificate Authority - G2+=========================================+-----BEGIN CERTIFICATE-----+MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT+B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s+b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0+eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw+DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg+VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB+dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv+W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs+bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk+N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf+ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU+JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC+AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol+TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx+4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw+F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K+pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ+c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0+-----END CERTIFICATE-----++Starfield Services Root Certificate Authority - G2+==================================================+-----BEGIN CERTIFICATE-----+MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT+B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s+b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl+IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV+BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT+dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg+Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC+AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2+h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa+hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP+LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB+rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG+SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP+E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy+xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd+iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza+YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6+-----END CERTIFICATE-----++AffirmTrust Commercial+======================+-----BEGIN CERTIFICATE-----+MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS+BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw+MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly+bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF+AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb+DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV+C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6+BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww+MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV+HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC+AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG+hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi+qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv+0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh+sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=+-----END CERTIFICATE-----++AffirmTrust Networking+======================+-----BEGIN CERTIFICATE-----+MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS+BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw+MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly+bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF+AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE+Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI+dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24+/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb+h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV+HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC+AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu+UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6+12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23+WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9+/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=+-----END CERTIFICATE-----++AffirmTrust Premium+===================+-----BEGIN CERTIFICATE-----+MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS+BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy+OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy+dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A+MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn+BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV+5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd+GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R+p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI+S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04+6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5+/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv+MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg+Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC+6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S+L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV+BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg+IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60+g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb+zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw==+-----END CERTIFICATE-----++AffirmTrust Premium ECC+=======================+-----BEGIN CERTIFICATE-----+MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV+BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx+MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U+cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA+IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ+N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW+BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK+BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X+57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM+eQ==+-----END CERTIFICATE-----++Certum Trusted Network CA+=========================+-----BEGIN CERTIFICATE-----+MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK+ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv+biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy+MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU+ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5+MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC+AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC+l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J+J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4+fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0+cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB+Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw+DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj+jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1+mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj+Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI+03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=+-----END CERTIFICATE-----++Certinomis - Autorité Racine+============================+-----BEGIN CERTIFICATE-----+MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjETMBEGA1UEChMK+Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAkBgNVBAMMHUNlcnRpbm9taXMg+LSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkG+A1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYw+JAYDVQQDDB1DZXJ0aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQAD+ggIPADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jYF1AMnmHa+wE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N8y4oH3DfVS9O7cdxbwly+Lu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWerP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw+2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92N+jMD2AR5vpTESOH2VwnHu7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9q+c1pkIuVC28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6lSTC+lrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1Enn1So2+WLhl+HPNb+xxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB0iSVL1N6aaLwD4ZFjliCK0wi1F6g+530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql095gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna+4NH4+ej9Uji29YnfAgMBAAGjWzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G+A1UdDgQWBBQNjLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ+KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9sov3/4gbIOZ/x+WqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZMOH8oMDX/nyNTt7buFHAAQCva+R6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40+nJ+U8/aGH88bc62UeYdocMMzpXDn2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1B+CxMjidPJC+iKunqjo3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjv+JL1vnxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG5ERQL1TE+qkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWqpdEdnV1j6CTmNhTih60b+WfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZbdsLLO7XSAPCjDuGtbkD326C00EauFddE+wk01+dIL8hf2rGbVJLJP0RyZwG71fet0BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/+vgt2Fl43N+bYdJeimUV5+-----END CERTIFICATE-----++TWCA Root Certification Authority+=================================+-----BEGIN CERTIFICATE-----+MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ+VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh+dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG+EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB+IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK+AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx+QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC+oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP+4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r+y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB+BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG+9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC+mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW+QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY+T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny+Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==+-----END CERTIFICATE-----++Security Communication RootCA2+==============================+-----BEGIN CERTIFICATE-----+MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc+U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh+dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC+SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy+aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB+ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R+3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV+spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K+EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8+QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB+CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj+u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk+3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q+tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29+mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03+-----END CERTIFICATE-----++EC-ACC+======+-----BEGIN CERTIFICATE-----+MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE+BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w+ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD+VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE+CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT+BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7+MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt+SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl+Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh+cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK+w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT+ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4+HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a+E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw+0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD+VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0+Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l+dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ+lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa+Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe+l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2+E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D+5EI=+-----END CERTIFICATE-----++Hellenic Academic and Research Institutions RootCA 2011+=======================================================+-----BEGIN CERTIFICATE-----+MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT+O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y+aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z+IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT+AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z+IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo+IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB+AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI+1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa+71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u+8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH+3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/+MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8+MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu+b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt+XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8+TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD+/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N+7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4+-----END CERTIFICATE-----++Actalis Authentication Root CA+==============================+-----BEGIN CERTIFICATE-----+MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM+BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE+AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky+MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz+IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290+IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ+wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa+by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6+zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f+YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2+oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l+EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7+hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8+EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5+jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY+iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt+ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI+WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0+JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx+K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU++Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC+4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo+2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz+lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem+OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9+vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==+-----END CERTIFICATE-----++Trustis FPS Root CA+===================+-----BEGIN CERTIFICATE-----+MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQG+EwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQUyBSb290+IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNV+BAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJ+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQ+RUN+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihHiTHcDnlk+H5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjjvSkCqPoc4Vu5g6hBSLwa+cY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zt+o3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEA+AaNTMFEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAd+BgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01GX2c+GE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOC+yinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP41BIy+Q7DsdwyhEQsb8tGD+pmQQ9P+8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZEf1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHV+l/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl+iB6XzCGcKQENZetX2fNXlrtIzYE=+-----END CERTIFICATE-----++StartCom Certification Authority+================================+-----BEGIN CERTIFICATE-----+MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN+U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu+ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0+NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk+LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg+U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw+ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y+o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/+Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d+eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt+2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z+6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ+osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/+untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc+UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT+37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD+VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQ+Qa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0+dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cu+c3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENv+bW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0+aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0+aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRzc2wuY29t+L3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBG+cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5+fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWm+N3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcN+Org+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6T+tn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAX+e2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA+2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBs+HvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE+JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdib+D4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8=+-----END CERTIFICATE-----++StartCom Certification Authority G2+===================================+-----BEGIN CERTIFICATE-----+MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMN+U3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg+RzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UE+ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp+dHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8O+o1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsDvfOpL9HG+4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnooD/Uefyf3lLE3PbfHkffi+Aez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/Q0kGi4xDuFby2X8hQxfqp0iVAXV16iul+Q5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbs+O+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8H+vKTlXcxNnw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4L+nMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iS+FGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMa+z6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8E+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJ+KoZIhvcNAQELBQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K+2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbk+J4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl++JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG+/+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTc+nIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/Xld+blhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5lIxKVCCIc+l85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoohdVddLHRDiBYmxOlsGOm+7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjRkcfGEvRM/TAXw8HaOFvjqerm+obp573PYtlNXLfbQ4ddI+-----END CERTIFICATE-----++Buypass Class 2 Root CA+=======================+-----BEGIN CERTIFICATE-----+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU+QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X+DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1+eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1+g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn+9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b+/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU+CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff+awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI+zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn+Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX+Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs+M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD+VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF+AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s+A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI+osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S+aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd+DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD+LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0+oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC+wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS+CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN+rJgWVqA=+-----END CERTIFICATE-----++Buypass Class 3 Root CA+=======================+-----BEGIN CERTIFICATE-----+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU+QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X+DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1+eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH+sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR+5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh+7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ+ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH+2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV+/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ+RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA+Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq+j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD+VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF+AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV+cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G+uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG+Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8+ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2+KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz+6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug+UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe+eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi+Cp/HuZc=+-----END CERTIFICATE-----++T-TeleSec GlobalRoot Class 3+============================+-----BEGIN CERTIFICATE-----+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM+IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU+cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx+MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz+dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD+ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3+DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK+9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU+NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF+iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W+0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr+AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb+fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT+ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h+P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml+e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw==+-----END CERTIFICATE-----++EE Certification Centre Root CA+===============================+-----BEGIN CERTIFICATE-----+MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG+EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy+dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw+MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB+UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy+ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB+DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM+TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2+rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw+93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN+P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T+AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ+MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF+BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj+xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM+lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u+uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU+3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM+dcGWxZ0=+-----END CERTIFICATE-----++TURKTRUST Certificate Services Provider Root 2007+=================================================+-----BEGIN CERTIFICATE-----+MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOcUktUUlVTVCBF+bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP+MA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg+QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4X+DTA3MTIyNTE4MzcxOVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxl+a3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMCVFIxDzAN+BgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2ltIHZlIEJp+bGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4gKGMpIEFyYWzEsWsgMjAwNzCCASIw+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9N+YvDdE3ePYakqtdTyuTFYKTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQv+KUmi8wUG+7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveGHtya+KhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6PIzdezKKqdfcYbwnT+rqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M733WB2+Y8a+xwXrXgTW4qhe04MsC+AwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHkYb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAP+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/s+Px+EnWVUXKgWAkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I+aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5mxRZNTZPz/OO+Xl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsaXRik7r4EW5nVcV9VZWRi1aKb+BFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZqxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAK+poRq0Tl9+-----END CERTIFICATE-----++D-TRUST Root Class 3 CA 2 2009+==============================+-----BEGIN CERTIFICATE-----+MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK+DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe+Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE+LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD+ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA+BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv+KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z+p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC+AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ+4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y+eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw+MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G+PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw+OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm+2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0+o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV+dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph+X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I=+-----END CERTIFICATE-----++D-TRUST Root Class 3 CA 2 EV 2009+=================================+-----BEGIN CERTIFICATE-----+MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK+DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw+OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK+DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw+OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS+egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh+zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T+7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60+sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35+11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv+cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v+ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El+MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp+b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh+c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P++PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05+nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX+ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA+NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv+w9y4AyHqnxbxLFS1+-----END CERTIFICATE-----++PSCProcert+==========+-----BEGIN CERTIFICATE-----+MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1dG9yaWRhZCBk+ZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9sYW5vMQswCQYDVQQGEwJWRTEQ+MA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlzdHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lz+dGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBl+cmludGVuZGVuY2lhIGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUw+IwYJKoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEwMFoXDTIw+MTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHByb2NlcnQubmV0LnZlMQ8w+DQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGExKjAoBgNVBAsTIVByb3ZlZWRvciBkZSBD+ZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZp+Y2FjaW9uIEVsZWN0cm9uaWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIw+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo97BVC+wfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74BCXfgI8Qhd19L3uA+3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38GieU89RLAu9MLmV+QfI4tL3czkkoh+RqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmO+EO8GqQKJ/+MMbpfg353bIdD0PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG2+0qCZyFSTXai20b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH+0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/6mnbVSKVUyqU+td+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1mv6JpIzi4mWCZDlZTOpx+FIyw+Bm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvp+r2uKGcfLFFb14dq12fy/czja+eevbqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/+AgEBMDcGA1UdEgQwMC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAz+Ni0wMB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFDgBStuyId+xuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0b3JpZGFkIGRlIENlcnRp+ZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xhbm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQH+EwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5h+Y2lvbmFsIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5k+ZW5jaWEgZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkqhkiG+9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQDAgEGME0GA1UdEQRG+MESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0wMDAwMDKgGwYFYIZeAgKgEgwQUklG+LUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEagRKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52+ZS9sY3IvQ0VSVElGSUNBRE8tUkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNy+YWl6LnN1c2NlcnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v+Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsGAQUFBwIBFh5o+dHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcNAQELBQADggIBACtZ6yKZu4Sq+T96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmN+g7+mvTV+LFwxNG9s2/NkAZiqlCxB3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4q+uxtxj7mkoP3YldmvWb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1+n8GhHVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHmpHmJWhSn+FFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXzsOfIt+FTvZLm8wyWuevo+5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bEqCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq+3TNWOByyrYDT13K9mmyZY+gAu0F2BbdbmRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5+poLWccret9W6aAjtmcz9opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3Y+eMLEYC/HYvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km+-----END CERTIFICATE-----++China Internet Network Information Center EV Certificates Root+==============================================================+-----BEGIN CERTIFICATE-----+MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMCQ04xMjAwBgNV+BAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyMUcwRQYDVQQDDD5D+aGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMg+Um9vdDAeFw0xMDA4MzEwNzExMjVaFw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAG+A1UECgwpQ2hpbmEgSW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMM+PkNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRpZmljYXRl+cyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z7r07eKpkQ0H1UN+U8i6y+jUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV+98YPjUesWgbdYavi7NifFy2cyjw1l1VxzUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2H+klY0bBoQCxfVWhyXWIQ8hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23+KzhmBsUs4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54ugQEC+7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oYNJKiyoOCWTAPBgNV+HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUfHJLOcfA22KlT5uqGDSSosqD+glkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd5+0XPFtQO3WKwMVC/GVhMPMdoG52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM+7+czV0I664zBechNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws+ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrIzo9uoV1/A3U0+5K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATywy39FCqQmbkHzJ8=+-----END CERTIFICATE-----++Swisscom Root CA 2+==================+-----BEGIN CERTIFICATE-----+MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQG+EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy+dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2+MjUwNzM4MTRaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln+aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIIC+IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvErjw0DzpPM+LgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r0rk0X2s682Q2zsKwzxNo+ysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJ+wDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVPACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpH+Wrumnf2U5NGKpV+GY3aFy6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1a+SgJA/MTAtukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL6yxS+NLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0uPoTXGiTOmekl9Ab+mbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrALacywlKinh/LTSlDcX3KwFnUey7QY+Ypqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velhk6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3+qPyZ7iVNTA6z00yPhOgpD/0QVAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw+HQYDVR0hBBYwFDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O+BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqhb97iEoHF8Twu+MA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4RfbgZPnm3qKhyN2abGu2sEzsO+v2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ+82YqZh6NM4OKb3xuqFp1mrjX2lhIREeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLz+o9v/tdhZsnPdTSpxsrpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcs+a0vvaGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciATwoCqISxx+OQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99nBjx8Oto0QuFmtEYE3saW+mA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5Wt6NlUe07qxS/TFED6F+KBZvuim6c779o++sjaC+NCydAXFJy3SuCvkychVSa1ZC+N8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TC+rvJcwhbtkj6EPnNgiLx29CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX+5OfNeOI5wSsSnqaeG8XmDtkx2Q==+-----END CERTIFICATE-----++Swisscom Root EV CA 2+=====================+-----BEGIN CERTIFICATE-----+MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAwZzELMAkGA1UE+BhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdpdGFsIENlcnRpZmljYXRlIFNl+cnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcN+MzEwNjI1MDg0NTA4WjBnMQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsT+HERpZ2l0YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYg+Q0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7BxUglgRCgz+o3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD1ycfMQ4jFrclyxy0uYAy+Xhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPHoCE2G3pXKSinLr9xJZDzRINpUKTk4Rti+GZQJo/PDvO/0vezbE53PnUgJUmfANykRHvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8Li+qG12W0OfvrSdsyaGOx9/5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaH+Za0zKcQvidm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHLOdAG+alNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaCNYGu+HuB5ur+rPQa+m3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f46Fq9mDU5zXNysRojddxyNMkM3Ox+bPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCBUWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDi+xzgHcgplwLa7JSnaFp6LNYth7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/+BAQDAgGGMB0GA1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED+MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWBbj2ITY1x0kbB+bkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6xXCX5145v9Ydkn+0UjrgEjihL+j6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98TPLr+flaYC/NUn81ETm484T4VvwYmneTwkLbU+wp4wLh/vx3rEUMfqe9pQy3omywC0Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7+XwgiG/W9mR4U9s70WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH+59yLGn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm7JFe3VE/+23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4Snr8PyQUQ3nqjsTzyP6Wq+J3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VNvBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyA+HmBR3NdUIR7KYndP+tiPsys6DXhyyWhBWkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/gi+uMod89a2GQ+fYWVq6nTIfI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuW+l8PVP3wbI+2ksx0WckNLIOFZfsLorSa/ovc=+-----END CERTIFICATE-----++CA Disig Root R1+================+-----BEGIN CERTIFICATE-----+MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNVBAYTAlNLMRMw+EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp+ZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQyMDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sx+EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp+c2lnIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy+3QRkD2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/oOI7bm+V8+u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3AfQ+lekLZWnDZv6fXARz2+m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJeIgpFy4QxTaz+29FHuvlglzmxZcfe+5nk+CiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8noc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTa+YVKvJrT1cU/J19IG32PK/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6+vpmumwKjrckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD3AjL+LhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE7cderVC6xkGbrPAX+ZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkCyC2fg69naQanMVXVz0tv/wQFx1is+XxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLdqvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNV+HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ+04IwDQYJKoZIhvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR+xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaASfX8MPWbTx9B+LxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXoHqJPYNcHKfyyo6SdbhWSVhlM+CrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpBemOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5Gfb+VSUZP/3oNn6z4eGBrxEWi1CXYBmCAMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85+YmLLW1AL14FABZyb7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKS+ds+xDzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvkF7mGnjix+lAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqFa3qdnom2piiZk4hA9z7N+UaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsTQ6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJ+a7+h89n07eLw4+1knj0vllJPgFOL+-----END CERTIFICATE-----++CA Disig Root R2+================+-----BEGIN CERTIFICATE-----+MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw+EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp+ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx+EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp+c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC+w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia+xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7+A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S+GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV+g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa+5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE+koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A+Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i+Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV+HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u+Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM+tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV+sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je+dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8+1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx+mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01+utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0+sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg+UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV+7+ZtsH8tZ/3zbBt1RqPlShfppNcL+-----END CERTIFICATE-----++ACCVRAIZ1+=========+-----BEGIN CERTIFICATE-----+MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB+SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1+MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH+UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC+DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM+jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0+RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD+aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ+0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG+WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7+8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR+5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J+9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK+Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw+Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu+Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2+VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM+Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA+QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh+AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA+YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj+AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA+IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk+aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0+dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2+MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI+hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E+R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN+YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49+nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ+TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3+sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h+I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg+Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd+3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p+EfbRD0tVNEYqi4Y7+-----END CERTIFICATE-----++TWCA Global Root CA+===================+-----BEGIN CERTIFICATE-----+MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT+CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD+QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK+EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg+Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C+nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV+r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR+Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV+tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W+KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99+sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p+yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn+kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI+zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC+AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g+cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn+LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M+8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg+/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg+lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP+A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m+i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8+EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3+zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0=+-----END CERTIFICATE-----++TeliaSonera Root CA v1+======================+-----BEGIN CERTIFICATE-----+MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE+CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4+MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW+VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK++6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA+3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k+B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn+Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH+oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3+F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ+oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7+gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc+TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB+AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW+DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm+zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx+0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW+pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV+G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc+c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT+JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2+qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6+Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems+WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=+-----END CERTIFICATE-----++E-Tugra Certification Authority+===============================+-----BEGIN CERTIFICATE-----+MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w+DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls+ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN+ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw+NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx+QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl+cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD+DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A+MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd+hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K+CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g+ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ+BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0+E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz+rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq+jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn+rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5+dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB+/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG+MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK+kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO+XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807+VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo+a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc+dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV+KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT+Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0+8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G+C7TbO6Orb1wdtn7os4I07QZcJA==+-----END CERTIFICATE-----++T-TeleSec GlobalRoot Class 2+============================+-----BEGIN CERTIFICATE-----+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM+IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU+cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx+MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz+dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD+ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3+DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ+SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F+vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970+2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV+WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy+YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4+r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf+vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR+3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN+9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg==+-----END CERTIFICATE-----++Atos TrustedRoot 2011+=====================+-----BEGIN CERTIFICATE-----+MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU+cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4+MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG+A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV+hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr+54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH++DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320+HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR+z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R+l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ+bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB+CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h+k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh+TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9+61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G+3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed+-----END CERTIFICATE-----++QuoVadis Root CA 1 G3+=====================+-----BEGIN CERTIFICATE-----+MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG+A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv+b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN+MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg+RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE+PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm+PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6+Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN+ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l+g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV+7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX+9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f+iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg+t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD+AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI+hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC+MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3+GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct+Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP++V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh+3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa+wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6+O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0+FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV+hMJKzRwuJIczYOXD+-----END CERTIFICATE-----++QuoVadis Root CA 2 G3+=====================+-----BEGIN CERTIFICATE-----+MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG+A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv+b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN+MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg+RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh+ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY+NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t+oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o+MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l+V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo+L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ+sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD+6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh+lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD+AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI+hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66+AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K+pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9+x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz+dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X+U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw+mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD+zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN+JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr+O3jtZsSOeWmD3n+M+-----END CERTIFICATE-----++QuoVadis Root CA 3 G3+=====================+-----BEGIN CERTIFICATE-----+MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG+A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv+b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN+MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg+RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286+IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL+Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe+6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3+I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U+VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7+5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi+Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM+dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt+rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD+AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI+hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px+KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS+t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ+TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du+DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib+Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD+hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX+0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW+dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2+PpxxVJkES/1Y+Zj0+-----END CERTIFICATE-----++DigiCert Assured ID Root G2+===========================+-----BEGIN CERTIFICATE-----+MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw+IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw+MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL+ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH+35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq+bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw+VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP+YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn+lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO+w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv+0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz+d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW+hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M+jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo+IhNzbM8m9Yop5w==+-----END CERTIFICATE-----++DigiCert Assured ID Root G3+===========================+-----BEGIN CERTIFICATE-----+MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV+UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD+VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1+MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3+d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ+BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb+RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs+KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF+UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy+YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy+1vUhZscv6pZjamVFkpUBtA==+-----END CERTIFICATE-----++DigiCert Global Root G2+=======================+-----BEGIN CERTIFICATE-----+MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw+HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx+MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3+dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ+kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO+3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV+BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM+UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu+5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr+F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U+WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH+QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/+iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl+MrY=+-----END CERTIFICATE-----++DigiCert Global Root G3+=======================+-----BEGIN CERTIFICATE-----+MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV+UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD+VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw+MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k+aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C+AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O+YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP+BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp+Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y+3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34+VOKa5Vt8sycX+-----END CERTIFICATE-----++DigiCert Trusted Root G4+========================+-----BEGIN CERTIFICATE-----+MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw+HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1+MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3+d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G+CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp+pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o+k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa+vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY+QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6+MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm+mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7+f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH+dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8+oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud+DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD+ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY+ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr+yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy+7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah+ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN+5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb+/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa+5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK+G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP+82Z++-----END CERTIFICATE-----++WoSign+======+-----BEGIN CERTIFICATE-----+MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQG+EwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNVBAMTIUNlcnRpZmljYXRpb24g+QXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJ+BgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNh+dGlvbiBBdXRob3JpdHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA+vcqNrLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1UfcIiePyO+CbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcSccf+Hb0v1naMQFXQoOXXDX+2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2ZjC1vt7tj/id07sBMOby8w7gLJKA84X5+KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4Mx1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR++ScPewavVIMYe+HdVHpRaG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ez+EC8wQjchzDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDaruHqk+lWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221KmYo0SLwX3OSACCK2+8jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvASh0JWzko/amrzgD5LkhLJuYwTKVY+yrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWvHYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0C+AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R+8bNLtwYgFP6HEtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1+LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJMuYhOZO9sxXq+T2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2eJXLOC62qx1ViC777Y7NhRCOj+y+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VNg64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC+2nz4SNAzqfkHx5Xh9T71XXG68pWpdIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes+5cVAWubXbHssw1abR80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/+EaEQPkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGcexGATVdVh+mVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+J7x6v+Db9NpSvd4MVHAx+kUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMlOtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGi+kpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWTee5Ehr7XHuQe+w==+-----END CERTIFICATE-----++WoSign China+============+-----BEGIN CERTIFICATE-----+MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQG+EwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNVBAMMEkNBIOayg+mAmuagueiv+geS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgwMTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYD+VQQKExFXb1NpZ24gQ0EgTGltaXRlZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjAN+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k+8H/rD195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld19AXbbQs5+uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExfv5RxadmWPgxDT74wwJ85+dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnkUkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5+Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+LNVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFy+b7Ao65vh4YOhn0pdr8yb+gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc+76DbT52VqyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6KyX2m++Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0GAbQOXDBGVWCvOGU6+yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaKJ/kR8slC/k7e3x9cxKSGhxYzoacX+GKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwECAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1Ud+EwEB/wQFMAMBAf8wHQYDVR0OBBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUA+A4ICAQBqinA4WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6+yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj/feTZU7n85iY+r83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6jBAyvd0zaziGfjk9DgNyp115+j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0A+kLppRQjbbpCBhqcqBT/mhDn4t/lXX0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97+qA4bLJyuQHCH2u2nFoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Y+jj4Du9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10lO1Hm13ZB+ONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Leie2uPAmvylezkolwQOQv+T8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR12KvxAmLBsX5VYc8T1yaw15zLKYs4SgsO+kI26oQ==+-----END CERTIFICATE-----++COMODO RSA Certification Authority+==================================+-----BEGIN CERTIFICATE-----+MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE+BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG+A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv+biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC+R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE+ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB+dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn+dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ+FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt++5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG+x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX+2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL+OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3+sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C+GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5+WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E+FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w+DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt+rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj++nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg+tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW+sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp+pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA+zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq+ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52+7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I+LaZRfyHBNVOFBkpdn627G190+-----END CERTIFICATE-----++USERTrust RSA Certification Authority+=====================================+-----BEGIN CERTIFICATE-----+MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE+BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK+ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh+dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE+BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK+ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh+dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz+0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j+Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn+RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O++T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq+/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE+Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM+lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8+yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6++eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd+BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF+MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW+FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ+7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ+Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM+8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi+FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi+yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c+J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw+sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx+Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9+-----END CERTIFICATE-----++USERTrust ECC Certification Authority+=====================================+-----BEGIN CERTIFICATE-----+MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC+VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU+aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv+biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC+VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU+aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv+biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2+0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez+nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV+HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB+HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu+9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=+-----END CERTIFICATE-----++GlobalSign ECC Root CA - R4+===========================+-----BEGIN CERTIFICATE-----+MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEkMCIGA1UECxMb+R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD+EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb+R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD+EwpHbG9iYWxTaWduMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprl+OQcJFspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAwDgYDVR0P+AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61FuOJAf/sKbvu+M8k8o4TV+MAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGXkPoUVy0D7O48027KqGx2vKLeuwIgJ6iF+JzWbVsaj8kfSt24bAgAXqmemFZHe+pTsewv4n4Q=+-----END CERTIFICATE-----++GlobalSign ECC Root CA - R5+===========================+-----BEGIN CERTIFICATE-----+MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb+R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD+EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb+R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD+EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6+SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS+h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd+BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx+uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7+yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3+-----END CERTIFICATE-----++Staat der Nederlanden Root CA - G3+==================================+-----BEGIN CERTIFICATE-----+MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE+CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g+Um9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloXDTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMC+TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l+ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4y+olQPcPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WWIkYFsO2t+x1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqXxz8ecAgwoNzFs21v0IJy+EavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFyKJLZWyNtZrVtB0LrpjPOktvA9mxjeM3K+Tj215VKb8b475lRgsGYeCasH/lSJEULR9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUur+mkVLoR9BvUhTFXFkC4az5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU5+1nus6+N86U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7Ngzp+07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHPbMk7ccHViLVlvMDo+FxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXtBznaqB16nzaeErAMZRKQFWDZJkBE+41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTtXUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMB+AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleu+yjWcLhL75LpdINyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD+U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwpLiniyMMB8jPq+KqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8Ipf3YF3qKS9Ysr1YvY2WTxB1+v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixpgZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA+8KCWAg8zxXHzniN9lLf9OtMJgwYh/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b+8KKaa8MFSu1BYBQw0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0r+mj1AfsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq4BZ+Extq+1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR1VmiiXTTn74eS9fGbbeI+JG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/QFH1T/U67cjF68IeHRaVesd+QnGTbksV+tzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM94B7IWcnMFk=+-----END CERTIFICATE-----++Staat der Nederlanden EV Root CA+================================+-----BEGIN CERTIFICATE-----+MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJOTDEeMBwGA1UE+CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFhdCBkZXIgTmVkZXJsYW5kZW4g+RVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0yMjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5M+MR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRl+cmxhbmRlbiBFViBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkk+SzrSM4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nCUiY4iKTW+O0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3dZ//BYY1jTw+bbRcwJu+r+0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46prfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8+Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13lpJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gV+XJrm0w912fxBmJc+qiXbj5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr+08C+eKxCKFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS/ZbV+0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0XcgOPvZuM5l5Tnrmd+74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH1vI4gnPah1vlPNOePqc7nvQDs/nx+fRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrPpx9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNC+MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwa+ivsnuL8wbqg7MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI+eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u2dfOWBfoqSmu+c0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHSv4ilf0X8rLiltTMMgsT7B/Zq+5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTCwPTxGfARKbalGAKb12NMcIxHowNDXLldRqAN+b/9Zjr7dn3LDWyvfjFvO5QxGbJKyCqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tN+f1zuacpzEPuKqf2evTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi+5Dp6Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIaGl6I6lD4+WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeLeG9QgkRQP2YGiqtDhFZK+DyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGy+eUN51q1veieQA6TqJIc/2b3Z6fJfUEkc7uzXLg==+-----END CERTIFICATE-----++IdenTrust Commercial Root CA 1+==============================+-----BEGIN CERTIFICATE-----+MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG+EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS+b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES+MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB+IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld+hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/+mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi+1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C+XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl+3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy+NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV+WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg+xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix+uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC+AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI+hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH+6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg+ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt+ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV+YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX+feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro+kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe+2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz+Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R+cGzM7vRX+Bi6hG6H+-----END CERTIFICATE-----++IdenTrust Public Sector Root CA 1+=================================+-----BEGIN CERTIFICATE-----+MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG+EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv+ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV+UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS+b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy+P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6+Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI+rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf+qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS+mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn+ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh+LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v+iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL+4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B+Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw+DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj+t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A+mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt+GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt+m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx+NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4+Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI+ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC+ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ+3Wl9af0AVqW3rLatt8o+Ae+c+-----END CERTIFICATE-----++Entrust Root Certification Authority - G2+=========================================+-----BEGIN CERTIFICATE-----+MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV+BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy+bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug+b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw+HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT+DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx+OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s+eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP+/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz+HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU+s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y+TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx+AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6+0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z+iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ+Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi+nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P++vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO+e4pIb4tF9g==+-----END CERTIFICATE-----++Entrust Root Certification Authority - EC1+==========================================+-----BEGIN CERTIFICATE-----+MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx+FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn+YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl+ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5+IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw+FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs+LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg+dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt+IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy+AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef+9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE+FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h+vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8+kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G+-----END CERTIFICATE-----++CFCA EV ROOT+============+-----BEGIN CERTIFICATE-----+MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE+CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB+IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw+MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD+DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV+BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD+7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN+uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW+ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7+xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f+py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K+gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol+hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ+tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf+BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB+/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB+ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q+ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua+4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG+E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX+BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn+aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy+PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX+kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C+ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su+-----END CERTIFICATE-----++TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H5+====================================================+-----BEGIN CERTIFICATE-----+MIIEJzCCAw+gAwIBAgIHAI4X/iQggTANBgkqhkiG9w0BAQsFADCBsTELMAkGA1UEBhMCVFIxDzAN+BgNVBAcMBkFua2FyYTFNMEsGA1UECgxEVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2ltIHZlIEJp+bGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4xQjBABgNVBAMMOVTDnFJLVFJVU1Qg+RWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSBINTAeFw0xMzA0MzAw+ODA3MDFaFw0yMzA0MjgwODA3MDFaMIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0w+SwYDVQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnE+n2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBFbGVrdHJvbmlrIFNlcnRp+ZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB+CgKCAQEApCUZ4WWe60ghUEoI5RHwWrom/4NZzkQqL/7hzmAD/I0Dpe3/a6i6zDQGn1k19uwsu537+jVJp45wnEFPzpALFp/kRGml1bsMdi9GYjZOHp3GXDSHHmflS0yxjXVW86B8BSLlg/kJK9siArs1m+ep5Fimh34khon6La8eHBEJ/rPCmBp+EyCNSgBbGM+42WAA4+Jd9ThiI7/PS98wl+d+yG6w8z5UNP+9FR1bSmZLmZaQ9/LXMrI5Tjxfjs1nQ/0xVqhzPMggCTTV+wVunUlm+hkS7M0hO8EuPbJbKoCPrZV+4jI3X/xml1/N1p7HIL9Nxqw/dV8c7TKcfGkAaZHjIxhT6QIDAQABo0IwQDAdBgNVHQ4EFgQUVpkH+HtOsDGlktAxQR95DLL4gwPswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI+hvcNAQELBQADggEBAJ5FdnsXSDLyOIspve6WSk6BGLFRRyDN0GSxDsnZAdkJzsiZ3GglE9Rc8qPo+BP5yCccLqh0lVX6Wmle3usURehnmp349hQ71+S4pL+f5bFgWV1Al9j4uPqrtd3GqqpmWRgqujuwq+URawXs3qZwQcWDD1YIq9pr1N5Za0/EKJAWv2cMhQOQwt1WbZyNKzMrcbGW3LM/nfpeYVhDfwwvJl+lpKQd/Ct9JDpEXjXk4nAPQu6KfTomZ1yju2dL+6SfaHx/126M2CFYv4HAqGEVka+lgqaE9chTLd8+B59OTj+RdPsnnRHM3eaxynFNExc5JsUpISuTKWqW+qtB4Uu2NQvAmxU=+-----END CERTIFICATE-----++Certinomis - Root CA+====================+-----BEGIN CERTIFICATE-----+MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjETMBEGA1UEChMK+Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAbBgNVBAMTFENlcnRpbm9taXMg+LSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMzMTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIx+EzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRD+ZXJ0aW5vbWlzIC0gUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQos+P5L2fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJflLieY6pOo+d5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQVWZUKxkd8aRi5pwP5ynap+z8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDFTKWrteoB4owuZH9kb/2jJZOLyKIOSY00+8B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09x+RLWtwHkziOC/7aOgFLScCbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE+6OXWk6RiwsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJwx3t+FvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SGm/lg0h9tkQPTYKbV+PZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4F2iw4lNVYC2vPsKD2NkJK/DAZNuH+i5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZngWVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGj+YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I+6tNxIqSSaHh02TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF+AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/0KGRHCwPT5iV+WVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWwF6YSjNRieOpWauwK0kDDPAUw+Pk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZSg081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAX+lCOotQqSD7J6wWAsOMwaplv/8gzjqh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJ+y29SWwNyhlCVCNSNh4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9+Iff/ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8Vbtaw5Bng+DwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwjY/M50n92Uaf0yKHxDHYi+I0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nM+cyrDflOR1m749fPH0FFNjkulW+YZFzvWgQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVr+hkIGuUE=+-----END CERTIFICATE-----++OISTE WISeKey Global Root GB CA+===============================+-----BEGIN CERTIFICATE-----+MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG+EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl+ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw+MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD+VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds+b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX+scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP+rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk+9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o+Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg+GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB+/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI+hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD+dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0+VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui+HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic+Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=+-----END CERTIFICATE-----++Certification Authority of WoSign G2+====================================+-----BEGIN CERTIFICATE-----+MIIDfDCCAmSgAwIBAgIQayXaioidfLwPBbOxemFFRDANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQG+EwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxLTArBgNVBAMTJENlcnRpZmljYXRpb24g+QXV0aG9yaXR5IG9mIFdvU2lnbiBHMjAeFw0xNDExMDgwMDU4NThaFw00NDExMDgwMDU4NThaMFgx+CzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEtMCsGA1UEAxMkQ2VydGlm+aWNhdGlvbiBBdXRob3JpdHkgb2YgV29TaWduIEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB+CgKCAQEAvsXEoCKASU+/2YcRxlPhuw+9YH+v9oIOH9ywjj2X4FA8jzrvZjtFB5sg+OPXJYY1kBai+XW8wGQiHC38Gsp1ij96vkqVg1CuAmlI/9ZqD6TRay9nVYlzmDuDfBpgOgHzKtB0TiGsOqCR3A9Du+W/PKaZE1OVbFbeP3PU9ekzgkyhjpJMuSA93MHD0JcOQg5PGurLtzaaNjOg9FD6FKmsLRY6zLEPg9+5k4ot+vElbGs/V6r+kHLXZ1L3PR8du9nfwB6jdKgGlxNIuG12t12s9R23164i5jIFFTMaxeSt+BK+v0mUYQs4kI9dJGwlezt52eJ+na2fmKEG/HgUYFf47oB3sQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC+AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU+mCp62XF3RYUCE4MD42b4Pdkr2cwDQYJKoZI+hvcNAQELBQADggEBAFfDejaCnI2Y4qtAqkePx6db7XznPWZaOzG73/MWM5H8fHulwqZm46qwtyeY+P0nXYGdnPzZPSsvxFPpahygc7Y9BMsaV+X3avXtbwrAh449G3CE4Q3RM+zD4F3LBMvzIkRfEzFg3+TgvMWvchNSiDbGAtROtSjFA9tWwS1/oJu2yySrHFieT801LYYRf+epSEj3m2M1m6D8QL4nCgS3gu++sif/a+RZQp4OBXllxcU3fngLDT4ONCEIgDAFFEYKwLcMFrw6AF8NTojrwjkr6qOKEJJLvD1mTS++7Q9LGOHSJDy7XUe3IfKN0QqZjuNuPq1w4I+5ysxugTH2e5x6eeRncRg=+-----END CERTIFICATE-----++CA WoSign ECC Root+==================+-----BEGIN CERTIFICATE-----+MIICCTCCAY+gAwIBAgIQaEpYcIBr8I8C+vbe6LCQkDAKBggqhkjOPQQDAzBGMQswCQYDVQQGEwJD+TjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNVBAMTEkNBIFdvU2lnbiBFQ0MgUm9v+dDAeFw0xNDExMDgwMDU4NThaFw00NDExMDgwMDU4NThaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQK+ExFXb1NpZ24gQ0EgTGltaXRlZDEbMBkGA1UEAxMSQ0EgV29TaWduIEVDQyBSb290MHYwEAYHKoZI+zj0CAQYFK4EEACIDYgAE4f2OuEMkq5Z7hcK6C62N4DrjJLnSsb6IOsq/Srj57ywvr1FQPEd1bPiU+t5v8KB7FVMxjnRZLU8HnIKvNrCXSf4/CwVqCXjCLelTOA7WRf6qU0NGKSMyCBSah1VES1ns2o0Iw+QDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqv3VWqP2h4syhf3R+MluARZPzA7gwCgYIKoZIzj0EAwMDaAAwZQIxAOSkhLCB1T2wdKyUpOgOPQB0TKGXa/kNUTyh2Tv0+Daupn75OcsqF1NnstTJFGG+rrQIwfcf3aWMvoeGY7xMQ0Xk/0f7qO3/eVvSQsRUR2LIiFdAvwyYu+a/GRspBl9JrmkO5K+-----END CERTIFICATE-----++SZAFIR ROOT CA2+===============+-----BEGIN CERTIFICATE-----+MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG+A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV+BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ+BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD+VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q+qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK+DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE+2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ+ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi+ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P+AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC+AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5+O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67+oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul+4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6++/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw==+-----END CERTIFICATE-----++Certum Trusted Network CA 2+===========================+-----BEGIN CERTIFICATE-----+MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE+BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1+bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y+ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ+TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl+cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB+IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9+7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o+CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b+Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p+uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130+GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ+9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB+Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye+hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM+BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD+AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI+hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW+Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA+L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo+clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM+pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb+w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo+J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm+ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX+is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7+zAYspsbiDrW5viSP+-----END CERTIFICATE-----++Hellenic Academic and Research Institutions RootCA 2015+=======================================================+-----BEGIN CERTIFICATE-----+MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT+BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0+aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl+YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx+MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg+QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV+BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw+MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv+bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh+iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts++6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd+FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr+i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F+GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2+fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu+iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc+Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD+AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI+hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF++D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM+d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y+d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn+82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb+davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F+Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt+J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa+JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q+p/UsQu0yrbYhnr68+-----END CERTIFICATE-----++Hellenic Academic and Research Institutions ECC RootCA 2015+===========================================================+-----BEGIN CERTIFICATE-----+MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0+aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u+cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj+aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw+MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj+IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD+VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290+Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP+dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK+Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O+BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA+GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn+dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR+-----END CERTIFICATE-----++Certplus Root CA G1+===================+-----BEGIN CERTIFICATE-----+MIIFazCCA1OgAwIBAgISESBVg+QtPlRWhS2DN7cs3EYRMA0GCSqGSIb3DQEBDQUAMD4xCzAJBgNV+BAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBsdXMgUm9vdCBDQSBHMTAe+Fw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBaMD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhD+ZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBsdXMgUm9vdCBDQSBHMTCCAiIwDQYJKoZIhvcNAQEBBQAD+ggIPADCCAgoCggIBANpQh7bauKk+nWT6VjOaVj0W5QOVsjQcmm1iBdTYj+eJZJ+622SLZOZ5KmHN+r49aiZFluVj8tANfkT8tEBXgfs+8/H9DZ6itXjYj2JizTfNDnjl8KvzsiNWI7nC9hRYt6kuJPKNx+Qv4c/dMcLRC4hlTqQ7jbxofaqK6AJc96Jh2qkbBIb6613p7Y1/oA/caP0FG7Yn2ksYyy/yARujVj+BYZHYEMzkPZHogNPlk2dT8Hq6pyi/jQu3rfKG3akt62f6ajUeD94/vI4CTYd0hYCyOwqaK/1jpTv+LRN6HkJKHRUxrgwEV/xhc/MxVoYxgKDEEW4wduOU8F8ExKyHcomYxZ3MVwia9Az8fXoFOvpHgDm2+z4QTd28n6v+WZxcIbekN1iNQMLAVdBM+5S//Ds3EC0pd8NgAM0lm66EYfFkuPSi5YXHLtaW6uOrc+4nBvCGrch2c0798wct3zyT8j/zXhviEpIDCB5BmlIOklynMxdCm+4kLV87ImZsdo/Rmz5yCTmehd+4F6H50boJZwKKSTUzViGUkAksnsPmBIgJPaQbEfIDbsYIC7Z/fyL8inqh3SV4EJQeIQEQWGw9CEj+jy3LKCHyamz0GqbFFLQ3ZU+V/YDI+HLlJWvEYLF7bY5KinPOWftwenMGE9nTdDckQQoRb5fc5+R++ob0V8rqHDz1oihYHAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0G+A1UdDgQWBBSowcCbkahDFXxdBie0KlHYlwuBsTAfBgNVHSMEGDAWgBSowcCbkahDFXxdBie0KlHY+lwuBsTANBgkqhkiG9w0BAQ0FAAOCAgEAnFZvAX7RvUz1isbwJh/k4DgYzDLDKTudQSk0YcbX8ACh+66Ryj5QXvBMsdbRX7gp8CXrc1cqh0DQT+Hern+X+2B50ioUHj3/MeXrKls3N/U/7/SMNkPX0XtPG+YX2eEeAC7gkE2Qfdpoq3DIMku4NQkv5gdRE+2J2winq14J2by5BSS7CTKtQ+FjPlnsZlFT5kOwQ/+2wyPX1wdaR+v8+khjPPvl/aatxm2hHSco1S1cE5j2FddUyGbQJJD+tZ3VTNPZNX70Cxqjm0lpu+F+6ALEUz65noe8zDUa3qHpimOHZR4RKttjd5cUvpoUmRGywO6wT/gUITJDT5+rosuoD6o7BlXGEilX+CNQ314cnrUlZp5GrRHpejXDbl85IULFzk/bwg2D5zfHhMf1bfHEhYxQUqq/F3pN+aLHsIqKqkHWe+tUNy6mSjhEv9DKgma3GX7lZjZuhCVPnHHd/Qj1vfyDBviP4NxDMcU6ij/UgQ8uQKTuEVV/xuZDDC+VRHc6qnNSlSsKWNEz0pAoNZoWRsz+e86i9sgktxChL8Bq4fA1SCC28a5g4VCXA9DO2pJNdWY9BW/++mGBDAkgGNLQFwzLSABQ6XaCjGTXOqAHVcweMcDvOrRl++O/QmueD6i9a5jc2NvLi6Td11n0bt3++qsOR0C5CB8AMTVPNJLFMWx5R9N/pkvo=+-----END CERTIFICATE-----++Certplus Root CA G2+===================+-----BEGIN CERTIFICATE-----+MIICHDCCAaKgAwIBAgISESDZkc6uo+jF5//pAq/Pc7xVMAoGCCqGSM49BAMDMD4xCzAJBgNVBAYT+AkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBsdXMgUm9vdCBDQSBHMjAeFw0x+NDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBaMD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0+cGx1czEcMBoGA1UEAwwTQ2VydHBsdXMgUm9vdCBDQSBHMjB2MBAGByqGSM49AgEGBSuBBAAiA2IA+BM0PW1aC3/BFGtat93nwHcmsltaeTpwftEIRyoa/bfuFo8XlGVzX7qY/aWfYeOKmycTbLXku54uN+Am8xIk0G42ByRZ0OQneezs/lf4WbGOT8zC5y0xaTTsqZY1yhBSpsBqNjMGEwDgYDVR0PAQH/BAQD+AgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNqDYwJ5jtpMxjwjFNiPwyCrKGBZMB8GA1Ud+IwQYMBaAFNqDYwJ5jtpMxjwjFNiPwyCrKGBZMAoGCCqGSM49BAMDA2gAMGUCMHD+sAvZ94OX7PNV+HdTcswYO/jOYnYs5kGuUIe22113WTNchp+e/IQ8rzfcq3IUHnQIxAIYUFuXcsGXCwI4Un78kFmjl+vPl5adytRSv3tjFzzAalU5ORGpOucGpnutee5WEaXw==+-----END CERTIFICATE-----++OpenTrust Root CA G1+====================+-----BEGIN CERTIFICATE-----+MIIFbzCCA1egAwIBAgISESCzkFU5fX82bWTCp59rY45nMA0GCSqGSIb3DQEBCwUAMEAxCzAJBgNV+BAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5UcnVzdCBSb290IENBIEcx+MB4XDTE0MDUyNjA4NDU1MFoXDTM4MDExNTAwMDAwMFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoM+CU9wZW5UcnVzdDEdMBsGA1UEAwwUT3BlblRydXN0IFJvb3QgQ0EgRzEwggIiMA0GCSqGSIb3DQEB+AQUAA4ICDwAwggIKAoICAQD4eUbalsUwXopxAy1wpLuwxQjczeY1wICkES3d5oeuXT2R0odsN7fa+Yp6bwiTXj/HbpqbfRm9RpnHLPhsxZ2L3EVs0J9V5ToybWL0iEA1cJwzdMOWo010hOHQX/uMftk87+ay3bfWAfjH1MBcLrARYVmBSO0ZB3Ij/swjm4eTrwSSTilZHcYTSSjFR077F9jAHiOH3BX2pfJLKO+YheteSCtqx234LSWSE9mQxAGFiQD4eCcjsZGT44ameGPuY4zbGneWK2gDqdkVBFpRGZPTBKnjix9+xNRbxQA0MMHZmf4yzgeEtE7NCv82TWLxp2NX5Ntqp66/K7nJ5rInieV+mhxNaMbBGN4zK1FGSxyO+9z0M+Yo0FMT7MzUj8czxKselu7Cizv5Ta01BG2Yospb6p64KTrk5M0ScdMGTHPjgniQlQ/GbI4Kq+3ywgsNw2TgOzfALU5nsaqocTvz6hdLubDuHAk5/XpGbKuxs74zD0M1mKB3IDVedzagMxbm+WG+Oi+n6+Sx+31QrclTDsTBM8clq8cIqPQqwWyTBIjUtz9GVsnnB47ev1CI9sjgBPwvFEVVJSmdz7QdFG9+URQIOTfLHzSpMJ1ShC5VkLG631UAC9hWLbFJSXKAqWLXwPYYEQRVzXR7z2FwefR7LFxckvzluFqr+TJOVoSfupb7PcSNCupt2LQIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB+/zAdBgNVHQ4EFgQUl0YhVyE12jZVx/PxN3DlCPaTKbYwHwYDVR0jBBgwFoAUl0YhVyE12jZVx/Px+N3DlCPaTKbYwDQYJKoZIhvcNAQELBQADggIBAB3dAmB84DWn5ph76kTOZ0BP8pNuZtQ5iSas000E+PLuHIT839HEl2ku6q5aCgZG27dmxpGWX4m9kWaSW7mDKHyP7Rbr/jyTwyqkxf3kfgLMtMrpkZ2Cv+uVnN35pJ06iCsfmYlIrM4LvgBBuZYLFGZdwIorJGnkSI6pN+VxbSFXJfLkur1J1juONI5f6ELlgK+n0Md/rcYkoZDSw6cMoYsYPXpSOqV7XAp8dUv/TW0V8/bhUiZucJvbI/NeJWsZCj9VrDDb8O+WVLh+X4SPgPL0DTatdrOjteFkdjpY3H1PXlZs5VVZV6Xf8YpmMIzUUmI4d7S+KNfKNsSbBfD4Fdvb8e80+nR14SohWZ25g/4/Ii+GOvUKpMwpZQhISKvqxnUOOBZuZ2mKtVzazHbYNeS2WuOvyDEsMpZTGMKcm+GS3tTAZQMPH9WD25SxdfGbRqhFS0OE85og2WaMMolP3tLR9Ka0OWLpABEPs4poEL0L9109S5zvE/+bw4cHjdx5RiHdRk/ULlepEU0rbDK5uUTdg8xFKmOLZTW1YVNcxVPS/KyPu1svf0OnWZzsD2097+o+4BGkxK51CUpjAEggpsadCwmKtODmzj7HPiY46SvepghJAwSQiumPv+i2tCqjI40cHLI5kqiPAlxA+OXXUc0ECd97N4EOH1uS6SsNsEn/+KuYj1oxx+-----END CERTIFICATE-----++OpenTrust Root CA G2+====================+-----BEGIN CERTIFICATE-----+MIIFbzCCA1egAwIBAgISESChaRu/vbm9UpaPI+hIvyYRMA0GCSqGSIb3DQEBDQUAMEAxCzAJBgNV+BAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5UcnVzdCBSb290IENBIEcy+MB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAwMFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoM+CU9wZW5UcnVzdDEdMBsGA1UEAwwUT3BlblRydXN0IFJvb3QgQ0EgRzIwggIiMA0GCSqGSIb3DQEB+AQUAA4ICDwAwggIKAoICAQDMtlelM5QQgTJT32F+D3Y5z1zCU3UdSXqWON2ic2rxb95eolq5cSG++Ntmh/LzubKh8NBpxGuga2F8ORAbtp+Dz0mEL4DKiltE48MLaARf85KxP6O6JHnSrT78eCbY2albz+4e6WiWYkBuTNQjpK3eCasMSCRbP+yatcfD7J6xcvDH1urqWPyKwlCm/61UWY0jUJ9gNDlP7ZvyCV+eYCYitmJNbtRG6Q3ffyZO6v/v6wNj0OxmXsWEH4db0fEFY8ElggGQgT4hNYdvJGmQr5J1WqIP7wt+UdGejeBSzFfdNTVY27SPJIjki9/ca1TSgSuyzpJLHB9G+h3Ykst2Z7UJmQnlrBcUVXDGPKBWCgOz+3GIZ38i1MH/1PCZ1Eb3XG7OHngevZXHloM8apwkQHZOJZlvoPGIytbU6bumFAYueQ4xncyhZW+vj+3CzMpSZyYhK05pyDRPZRpOLAeiRXyg6lPzq1O4vldu5w5pLeFlwoW5cZJ5L+epJUzpM5ChaHvGOz+9bGTXOBut9Dq+WIyiET7vycotjCVXRIouZW+j1MY5aIYFuJWpLIsEPUdN6b4t/bQWVyJ98LVtZR0+0dX+G7bw5tYee9I8y6jj9RjzIR9u701oBnstXW5DiabA+aC/gh7PU3+06yzbXfZqfUAkBXKJOAGT+y3HCOV0GEfZvePg3DTmEJwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB+/zAdBgNVHQ4EFgQUajn6QiL35okATV59M4PLuG53hq8wHwYDVR0jBBgwFoAUajn6QiL35okATV59+M4PLuG53hq8wDQYJKoZIhvcNAQENBQADggIBAJjLq0A85TMCl38th6aP1F5Kr7ge57tx+4BkJamz+Gj5oXScmp7oq4fBXgwpkTx4idBvpkF/wrM//T2h6OKQQbA2xx6R3gBi2oihEdqc0nXGEL8pZ0keI+mUEiyTCYYW49qKgFbdEfwFFEVn8nNQLdXpgKQuswv42hm1GqO+qTRmTFAHneIWv2V6CG1wZy7HBG+S4tz3aAhdT7cHcCP009zHIXZ/n9iyJVvttN7jLpTwm+bREx50B1ws9efAvSyB7DH5fitIw6mVskp+EndI2S9G/Tvw/HRwkqWOOAgfZDC2t0v7NqwQjqBSM2OdAzVWxWm9xiNaJ5T2pBL4LTM8oValX9YZ+6e18CL13zSdkzJTaTkZQh+D5wVOAHrut+0dSixv9ovneDiK3PTNZbNTe9ZUGMg1RGUFcPk8G97kr+gCf2o6p6fAbhQ8MTOWIaNr3gKC6UAuQpLmBVrkA9sHSSXvAgZJY/X0VdiLWK2gKgW0VU3jg9CcCo+SmVGFvyqv1ROTVu+OEO3KMqLM6oaJbolXCkvW0pujOotnCr2BXbgd5eAiN1nE28daCSLT7d0geX0+YJ96Vdc+N9oWaz53rK4YcJUIeSkDiv7BO7M/Gg+kO14fWKGVyasvc0rQLW6aWQ9VGHgtPFGml4vm+u7JwqkwR3v98KzfUetF3NI/n+UL3PIEMS1IK+-----END CERTIFICATE-----++OpenTrust Root CA G3+====================+-----BEGIN CERTIFICATE-----+MIICITCCAaagAwIBAgISESDm+Ez8JLC+BUCs2oMbNGA/MAoGCCqGSM49BAMDMEAxCzAJBgNVBAYT+AkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5UcnVzdCBSb290IENBIEczMB4X+DTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAwMFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9w+ZW5UcnVzdDEdMBsGA1UEAwwUT3BlblRydXN0IFJvb3QgQ0EgRzMwdjAQBgcqhkjOPQIBBgUrgQQA+IgNiAARK7liuTcpm3gY6oxH84Bjwbhy6LTAMidnW7ptzg6kjFYwvWYpa3RTqnVkrQ7cG7DK2uu5B+ta1doYXM6h0UZqNnfkbilPPntlahFVmhTzeXuSIevRHr9LIfXsMUmuXZl5mjYzBhMA4GA1UdDwEB+/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRHd8MUi2I5DMlv4VBN0BBY3JWIbTAf+BgNVHSMEGDAWgBRHd8MUi2I5DMlv4VBN0BBY3JWIbTAKBggqhkjOPQQDAwNpADBmAjEAj6jcnboM+BBf6Fek9LykBl7+BFjNAk2z8+e2AcG+qj9uEwov1NcoG3GRvaBbhj5G5AjEA2Euly8LQCGzpGPta+3U1fJAuwACEl74+nBCZx4nxp5V2a+EEfOzmTk51V6s2N8fvB+-----END CERTIFICATE-----++ISRG Root X1+============+-----BEGIN CERTIFICATE-----+MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE+BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD+EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG+EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT+DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r+Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1+3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K+b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN+Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ+4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf+1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu+hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH+usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r+OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G+A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY+9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL+ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV+0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt+hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw+TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx+e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA+JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD+YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n+JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ+m+kXQ99b21/+jh5Xos1AnX5iItreGCc=+-----END CERTIFICATE-----++AC RAIZ FNMT-RCM+================+-----BEGIN CERTIFICATE-----+MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT+AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw+MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD+TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC+ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf+qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr+btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL+j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou+08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw+WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT+tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ+47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC+ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa+i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE+FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o+dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD+nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s+D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ+j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT+Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW++YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7+Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d+8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm+5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG+rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM=+-----END CERTIFICATE-----++Amazon Root CA 1+================+-----BEGIN CERTIFICATE-----+MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD+VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1+MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv+bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC+ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH+FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ+gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t+dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce+VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB+/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3+DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM+CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy+8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa+2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2+xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5+-----END CERTIFICATE-----++Amazon Root CA 2+================+-----BEGIN CERTIFICATE-----+MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD+VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1+MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv+bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC+ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4+kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp+N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9+AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd+fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx+kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS+btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0+Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN+c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf++3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw+DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA+A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE+YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW+xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ+gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW+aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV+Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3+KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi+JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw=+-----END CERTIFICATE-----++Amazon Root CA 3+================+-----BEGIN CERTIFICATE-----+MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG+EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy+NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ+MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB+f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr+Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43+rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc+eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw==+-----END CERTIFICATE-----++Amazon Root CA 4+================+-----BEGIN CERTIFICATE-----+MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG+EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy+NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ+MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN+/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri+83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV+HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA+MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1+AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA==+-----END CERTIFICATE-----++LuxTrust Global Root 2+======================+-----BEGIN CERTIFICATE-----+MIIFwzCCA6ugAwIBAgIUCn6m30tEntpqJIWe5rgV0xZ/u7EwDQYJKoZIhvcNAQELBQAwRjELMAkG+A1UEBhMCTFUxFjAUBgNVBAoMDUx1eFRydXN0IFMuQS4xHzAdBgNVBAMMFkx1eFRydXN0IEdsb2Jh+bCBSb290IDIwHhcNMTUwMzA1MTMyMTU3WhcNMzUwMzA1MTMyMTU3WjBGMQswCQYDVQQGEwJMVTEW+MBQGA1UECgwNTHV4VHJ1c3QgUy5BLjEfMB0GA1UEAwwWTHV4VHJ1c3QgR2xvYmFsIFJvb3QgMjCC+AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANeFl78RmOnwYoNMPIf5U2o3C/IPPIfOb9wm+Kb3FibrJgz337spbxm1Jc7TJRqMbNBM/wYlFV/TZsfs2ZUv7COJIcRHIbjuend+JZTemhfY7RBi2+xjcwYkSSl2l9QjAk5A0MiWtj3sXh306pFGxT4GHO9hcvHTy95iJMHZP1EMShduxq3sVs35a0VkBC+wGKSMKEtFZSg0iAGCW5qbeXrt77U8PEVfIvmTroTzEsnXpk8F12PgX8zPU/TPxvsXD/wPEx1bvKm+1Z3aLQdjAsZy6ZS8TEmVT4hSyNvoaYL4zDRbIvCGp4m9SAptZoFtyMhk+wHh9OHe2Z7d21vUKpkm+FRseTJIpgp7VkoGSQXAZ96Tlk0u8d2cx3Rz9MXANF5kM+Qw5GSoXtTBxVdUPrljhPS80m8+f9niF+wpN6cj5mj5wWEWCPnolvZ77gR1o7DJpni89Gxq44o/KnvObWhWszJHAiS8sIm7vI+AIpHb4gDEa/+a4ebsypmQjVGbKq6rfmYe+lQVRQxv7HaLe2ArWgk+2mr2HETMOZns4dA/Yl+8kPREd8vZS9kzl8U+ubG/Mb2HeFpZZYiq/FkySIbWTLkpS5XTdvN3JW1CHDiDTf2jX5t/Lax5Gw5CMZdjpPuKadUiDTSQ+MC6otOBttpSsvItO13D8xTiOZCXhTTmQzsmHhFhxAgMBAAGjgagwgaUwDwYDVR0TAQH/BAUwAwEB+/zBCBgNVHSAEOzA5MDcGByuBKwEBAQowLDAqBggrBgEFBQcCARYeaHR0cHM6Ly9yZXBvc2l0b3J5+Lmx1eHRydXN0Lmx1MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBT/GCh2+UgFLKGu8SsbK7JT++Et8szAdBgNVHQ4EFgQU/xgodvlIBSyhrvErGyuyU/hLfLMwDQYJKoZIhvcNAQELBQADggIBAGoZ+FO1uecEsh9QNcH7X9njJCwROxLHOk3D+sFTAMs2ZMGQXvw/l4jP9BzZAcg4atmpZ1gDlaCDdLnIN+H2pkMSCEfUmmWjfrRcmF9dTHF5kH5ptV5AzoqbTOjFu1EVzPig4N1qx3gf4ynCSecs5U89BvolbW+7MM3LGVYvlcAGvI1+ut7MV3CwRI9loGIlonBWVx65n9wNOeD4rHh4bhY79SV5GCc8JaXcozrhAIu+ZY+kt9J/Z93I055cqqmkoCUUBpvsT34tC38ddfEz2O3OuHVtPlu5mB0xDVbYQw8wkbIEa91WvpWA+VWe+2M2D2RjuLg+GLZKecBPs3lHJQ3gCpU3I+V/EkVhGFndadKpAvAefMLmx9xIX3eP/JEAdemrR+TxgKqpAd60Ae36EeRJIQmvKN4dFLRp7oRUKX6kWZ8+xm1QL68qZKJKrezrnK+T+Tb/mjuuqlPpmt+/f97mfVl7vBZKGfXkJWkE4SphMHozs51k2MavDzq1WQfLSoSOcbDWjLtR5EWDrw4wVDej8oqkDQc+7kGUnF4ZLvhFSZl0kbAEb+MEWrGrKqv+x9CWttrhSmQGbmBNvUJO/3jaJMobtNeWOWyu8Q6qp31I+iyBMz2TWuJdGsE7RKlY6oJO9r4Ak4Ap+58rVyuiFVdw2KuGUaJPHZnJED4AhMmwlxyOAgwrr+-----END CERTIFICATE-----++TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1+=============================================+-----BEGIN CERTIFICATE-----+MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT+D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr+IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g+TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp+ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD+VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt+c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth+bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11+IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A+MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8+6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc+wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0+3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9+WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU+ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ+KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh+AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc+lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R+e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j+q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM=+-----END CERTIFICATE-----
mysql-haskell.cabal view
@@ -1,87 +1,354 @@-name:                mysql-haskell-version:             0.8.4.3-synopsis:            pure haskell MySQL driver-description:         pure haskell MySQL driver-license:             BSD3-license-file:        LICENSE-author:              winterland1989-maintainer:          winterland1989@gmail.com-copyright:           (c) 2016 Winterland-category:            Database-build-type:          Simple-extra-source-files:  ChangeLog.md, README.md-cabal-version:       >=1.10-homepage:            https://github.com/winterland1989/mysql-haskell-bug-reports:         https://github.com/winterland1989/mysql-haskell/issues+cabal-version:      3.4+name:               mysql-haskell+version:            1.5.0+synopsis:           pure haskell MySQL driver+description:        pure haskell MySQL driver.+license:            BSD-3-Clause+license-file:       LICENSE+author:             winterland1989+maintainer:         hi@jappie.me+copyright:          (c) 2016 Winterland+category:           Database+build-type:         Simple+extra-doc-files:    ChangeLog.md+-- mozillaCAStorePath reads the bundle through getDataFileName, which only+-- finds files cabal installs: data-files, not extra-source-files.+data-files:         mozillaCAStore.pem+extra-source-files:+  README.md+  test/cert/ca-key.pem+  test/cert/ca.pem+  test/cert/client-cert.pem+  test/cert/client-key.pem+  test/cert/server-cert.pem+  test/cert/server-key.pem+  test/cert/server-req.pem+  test/json-data/buffer-builder.json+  test/json-data/dates-fract.json+  test/json-data/dates.json+  test/json-data/example.json+  test/json-data/geometry.json+  test/json-data/integers.json+  test/json-data/jp10.json+  test/json-data/jp100.json+  test/json-data/jp50.json+  test/json-data/numbers.json+  test/json-data/twitter1.json+  test/json-data/twitter10.json+  test/json-data/twitter100.json+  test/json-data/twitter20.json+  test/json-data/twitter50.json +homepage:           https://github.com/winterland1989/mysql-haskell+bug-reports:        https://github.com/winterland1989/mysql-haskell/issues+ source-repository head   type:     git-  location: git://github.com/winterland1989/mysql-haskell.git+  location: https://github.com/winterland1989/mysql-haskell +common common-options+  ghc-options:+    -Wall -Wincomplete-uni-patterns+    -Wincomplete-record-updates -Widentities -Wredundant-constraints+    -Wcpp-undef -fwarn-tabs -Wpartial-fields+    -Wunused-packages+    -fenable-th-splice-warnings+    -fno-omit-yields+  build-depends:+      base >=4.9.1.0 && <4.22.0+  default-language: GHC2021+ library-    exposed-modules:    Database.MySQL.Base-                    ,   Database.MySQL.TLS-                    ,   Database.MySQL.Protocol.Auth-                    ,   Database.MySQL.Protocol.Command-                    ,   Database.MySQL.Protocol.ColumnDef-                    ,   Database.MySQL.Protocol.Packet-                    ,   Database.MySQL.Protocol.MySQLValue-                    ,   Database.MySQL.Protocol.Escape-                    ,   Database.MySQL.BinLog-                    ,   Database.MySQL.BinLogProtocol.BinLogEvent-                    ,   Database.MySQL.BinLogProtocol.BinLogValue-                    ,   Database.MySQL.BinLogProtocol.BinLogMeta-                    ,   Database.MySQL.Connection-    other-modules:      Database.MySQL.Query-    build-depends:      base          >= 4.7 && < 5-                    ,   monad-loops   == 0.4.*-                    ,   network       >= 2.3 && < 4.0-                    ,   io-streams    >= 1.2 && < 2.0-                    ,   tcp-streams   >= 1.0 && < 1.1-                    ,   wire-streams  >= 0.1-                    ,   binary        >= 0.8.3-                    ,   binary-ieee754-                    ,   binary-parsers >= 0.2.1-                    ,   bytestring    >= 0.10.2.0-                    ,   text          >= 1.1 && < 1.3-                    ,   cryptonite    == 0.*-                    ,   memory        >= 0.14.4 && < 0.16-                    ,   time          >= 1.5.0-                    ,   scientific    == 0.3.*-                    ,   bytestring-lexing == 0.5.*-                    ,   blaze-textual     == 0.2.*-                    ,   word24            >= 1.0 && <= 3.0-                    ,   tls           >= 1.3.5 && < 1.6-                    ,   vector        >= 0.8+  import: common-options+  exposed-modules:+    Data.Binary.Parser+    Data.Binary.Parser.Numeric+    Data.Binary.Parser.Word8+    Data.Connection+    Data.Int.Int24+    Data.TLSSetting+    Data.Word.Word24+    Database.MariaDB+    Database.MySQL.Base+    Database.MySQL.BinLog+    Database.MySQL.BinLogProtocol.BinLogEvent+    Database.MySQL.BinLogProtocol.BinLogMeta+    Database.MySQL.BinLogProtocol.BinLogValue+    Database.MySQL.BinLogProtocol.CRC32+    Database.MySQL.Connection+    Database.MySQL.Decoder+    Database.MySQL.Field+    Database.MySQL.Protocol.Auth+    Database.MySQL.Protocol.ColumnDef+    Database.MySQL.Protocol.Command+    Database.MySQL.Protocol.Escape+    Database.MySQL.Protocol.FloatingPoint+    Database.MySQL.Protocol.MySQLValue+    Database.MySQL.Protocol.Packet+    Database.MySQL.Protocol.RawRow+    Database.MySQL.TLS+    System.IO.Streams.TCP+    System.IO.Streams.TLS -    default-language:    Haskell2010-    default-extensions:     DeriveDataTypeable-                        ,   DeriveGeneric-                        ,   MultiWayIf-                        ,   OverloadedStrings-    ghc-options:       -Wall+  other-modules:      Paths_mysql_haskell+  autogen-modules:    Paths_mysql_haskell+  hs-source-dirs:     src+  other-modules:      Database.MySQL.Query+  build-depends:+    -- The CRC32 table of binlog event checksums.+    array >=0.5 && <0.6,+    asn1-encoding >=0.9 && <0.10,+    asn1-types >=0.3 && <0.4,+    binary >=0.8.3 && <0.9,+    blaze-textual >=0.2 && <0.3,+    -- 0.11.2 added isValidUtf8, which checks a string column's bytes.+    bytestring >=0.11.2 && <0.12 || ^>=0.12.0,+    bytestring-lexing >=0.5 && <0.6,+    crypton-x509 >=1.5 && <2.0,+    crypton-x509-store >=1.5 && <2.0,+    crypton-x509-system >=1.5 && <2.0,+    data-default-class >=0.1.2 && <0.2 || ^>=0.2.0,+    deepseq >=1.4.6 && <1.5 || ^>=1.5.0,+    io-streams >=1.2 && <2.0,+    monad-loops >=0.4 && <0.5,+    network >=2.3 && <4.0,+    crypton-pem >=0.2.4 && <0.4,+    scientific >=0.3 && <0.4,+    text >=1.1 && <2.1 || ^>=2.1,+    time >=1.5.0 && <1.12 || ^>=1.12.2 || ^>=1.14,+    tls >=1.7.0 && <1.8 || ^>=1.8.0 || ^>=1.9.0 || ^>=2.0.0 || ^>=2.1.0 || ^>=2.2.0 || ^>=2.3.0 || ^>=2.4.0 || ^>=2.5.0,+    vector >=0.8 && <0.13 || ^>=0.13.0,+    word-compat >=0.0 && <0.1 +  -- Decision: numeric conversions go through unwitch's named functions, which+  -- return Maybe where a conversion can lose information, instead of+  -- fromIntegral, which wraps silently. Its base >=4.18 matches the CI matrix+  -- (GHC 9.6 and up).+  build-depends:+    unwitch ^>=3.1++  -- Decision: require crypton 2.0, the first release with its side-channel+  -- fixes (none backported to 1.x). Before it, P-384/P-521 point multiplication+  -- and DH exponentiation took time that followed the secret, and tls runs them+  -- on the client's ephemeral key whenever the server picks such a group. 2.0+  -- rather than 2.1.x, as 2.0 has every fix we reach.+  build-depends:+    crypton ^>=2.0.0 || ^>=2.1.0 || ^>=2.2.0,+    ram >=0.20 && <0.23++  default-extensions:+    DeriveDataTypeable+    DeriveGeneric+    MultiWayIf+    OverloadedStrings++ test-suite test-    type: exitcode-stdio-1.0-    main-is: Main.hs-    other-modules:      BinaryRow-                    ,   BinaryRowNew-                    ,   BinLog-                    ,   BinLogNew-                    ,   TextRow-                    ,   TextRowNew-                    ,   ExecuteMany-    hs-source-dirs: test-    build-depends:  mysql-haskell-                  , base-                  , bytestring-                  , tasty >= 0.11 && < 2.0-                  , tasty-hunit-                  , text-                  , io-streams-                  , time-                  , vector-    default-extensions:     MultiWayIf-                        ,   OverloadedStrings-    ghc-options:         -threaded-    default-language:    Haskell2010+  import: common-options+  type:               exitcode-stdio-1.0+  main-is:            Main.hs+  other-modules:+    Aeson+    AesonBP+    BinLogChecksum+    BinLogEventCodes+    BoundsCheck+    DecoderRoundtrip+    DirectDecoding+    Ed25519Scramble+    FieldRoundtrip+    FloatingPointText+    HandshakeGolden+    JSON+    PacketClassification+    Placeholders+    QC.ByteString+    QC.Combinator+    QC.Common+    Orphans+    ResultCharsets+    Sha1Scramble+    Sha256Scramble+    StringLiterals+    TCPStreams+    TextRowDecoding+    TypedValue+    Word24+    ZeroDates++  hs-source-dirs:     test+  build-depends:+    attoparsec < 1.0,+    base16-bytestring >=1.0 && <2.0,+    binary >=0.8,+    bytestring >=0.10,+    crypton,+    deepseq,+    directory < 2.0,+    filepath < 2.0,+    io-streams,+    mysql-haskell,+    network,+    QuickCheck >=2.7 && <3.0,+    quickcheck-instances < 1.0,+    ram,+    scientific >=0.3.0,+    tasty >=0.11 && <2.0,+    tasty-expected-failure < 1.0,+    tasty-hunit < 1.0,+    tasty-quickcheck >=0.8 && < 1.0,+    text,+    time,+    unordered-containers < 1.0,+    vector++  default-extensions:+    MultiWayIf+    OverloadedStrings++  ghc-options:        -threaded ++test-suite integration+  type:               exitcode-stdio-1.0+  main-is:            Integration.hs+  other-modules:+    AbandonedReplies+    BinaryRow+    BinaryRowNew+    BinLog+    BinLogNew+    BinLogRowEvents+    CABundle+    CachingSha2+    ExecuteMany+    FloatingPoint+    LargeRows+    Latin1Results+    MariaDBNegotiation+    MultipleResults+    MysqlTests+    NonUtf8Results+    ParameterEscaping+    PlaceholderLiterals+    QueryApi+    QueryWithoutResultSet+    ResetConnection+    RoundtripBit+    RowDecoderQueries+    RoundtripYear+    SelectOne+    TestUser+    TextRow+    TLSClientCertificate+    TLSConnection+    TextRowNew+    TypedValue+    UnixSocket+    UnsignedInsertId+    ZeroDateRoundtrip++  hs-source-dirs:     test+  build-depends:+    base,+    bytestring >=0.10,+    directory,+    filepath,+    io-streams,+    mysql-haskell,+    scientific,+    tasty >=0.11 && <2.0,+    tasty-hunit < 1.0,+    text,+    time,+    vector++  default-extensions:+    MultiWayIf+    OverloadedStrings++  ghc-options:        -threaded+  default-language:   Haskell2010++test-suite integration-mariadb+  type:               exitcode-stdio-1.0+  main-is:            IntegrationMariaDB.hs+  other-modules:+    MariaDBBulk+    MariaDBEd25519+    MariaDBExtendedMetadata+    QueryApi+    TypedValue+    UnixSocket++  hs-source-dirs:     test+  build-depends:+    base,+    bytestring >=0.10,+    directory,+    io-streams,+    mysql-haskell,+    tasty >=0.11 && <2.0,+    tasty-hunit < 1.0,+    text,+    vector++  default-extensions:+    MultiWayIf+    OverloadedStrings++  ghc-options:        -threaded+  default-language:   Haskell2010++benchmark binary-parsers-bench+  import: common-options+  other-modules:+    Aeson+    AesonBP+    Common+    HttpReq+    Network.Wai.Handler.Warp.ReadInt+    Network.Wai.Handler.Warp.RequestHeader++  build-depends:+    attoparsec,+    binary,+    bytestring,+    case-insensitive < 2,+    criterion >=1.1 && <1.2 || ^>=1.6.3,+    deepseq,+    directory < 1.4,+    filepath < 1.6,+    http-types < 1.0,+    mysql-haskell,+    scanner < 0.4,+    scientific,+    text,+    unordered-containers,+    vector++  hs-source-dirs:   binary-parser-bench+  main-is:          Bench.hs+  type:             exitcode-stdio-1.0+  ghc-options:      -O2++benchmark bench24+  import: common-options+  type:             exitcode-stdio-1.0+  hs-source-dirs:   word24-bench+  main-is:          Benchmark.hs+  build-depends:+    criterion >=1.1,+    deepseq >=1.2 && <2,+    mysql-haskell++  ghc-options:      -O2++-- enable to debug the RULES    +-- executable exe+--   import: common-options+--   main-is: Main.hs+--   hs-source-dirs:+--       app+--   build-depends:+--       mysql-haskell+--   ghc-options: -Wno-unused-packages
+ src/Data/Binary/Parser.hs view
@@ -0,0 +1,385 @@+{-# LANGUAGE BangPatterns #-}+-- |+-- Module      :  Data.Binary.Parser+-- Copyright   :  Daan Leijen 1999-2001, Bryan O'Sullivan 2007-2015, Winterland 2016+-- License     :  BSD3+--+-- Maintainer  :  drkoster@qq.com+-- Stability   :  experimental+-- Portability :  portable+--+-- This library provide parsec\/attoparsec style parsing combinators for+-- <http://hackage.haskell.org/package/binary binary>+-- package. By default, this module export combinators in "Data.Binary.Get",+-- "Data.Binary.Parser.Word8" and "Data.Binary.Parser.Numeric".+--+-- The behaviour of parsers here is different to that of the+-- similarly-named parser in Parsec, as this one is all-or-nothing.+-- To illustrate the difference, the following parser will fail under+-- Parsec given an input of @\"for\"@:+--+-- >string "foo" <|> string "for"+--+-- The reason for its failure is that the first branch is a+-- partial match, and will consume the letters @\'f\'@ and @\'o\'@+-- before failing.  In binary-parsers, the above parser will /succeed/ on+-- that input, because the failed first branch will consume nothing.+--+-- There're some redundant combinators get removed, for example:+--+-- @+-- choice == asum+-- count == replicateM+-- atEnd == isEmpty+-- take == getByteString+-- many1 == some+-- @+--+-- For fast byte set operations, please use <http://hackage.haskell.org/package/charset charset>+-- package.+--+-- It's recommanded to use 'parseOnly', 'parseDetail'... functions to run your parsers since these+-- functions are faster than binary's counter part by avoiding a small constant overhead.+-- Check 'parse' for detail.+--+-- = A few words on performance and backtracking+--+-- There's a common belief that parsers which support backtracking are slow, but it's not neccessarily+-- true in binary, because binary doesn't do book keeping if you doesn't use '<|>', 'lookAhead' or their+-- friends. Combinators in this library like 'peek', 'string'... also try to avoid backtracking so+-- it's faster to use them rather than do backtracking yourself, for example, 'peek' is faster than+-- @'lookAhead' 'getWord8'@. In practice, protocols are often designed to avoid backtracking.+-- For example, if you have following parser:+--+-- >branch1 <|> branch2 <|> (skipN 1 >> branch3)+--+-- And if you can select the right branch just by looking ahead one byte, then you can rewrite it to:+--+-- @+-- w <- peek+-- if  | w == b1 -> branch1+--     | w == b2 -> branch2+--     | w == b3 -> skipN 1 >> branch3+-- @+--+-- Binary performs as fast as a non-backtracking parser as long as you construct your parser+-- without using backtracking. And sometime backtracking is indeed neccessary, for example 'scientifically'+-- is almost impossible to implement correctly if you don't do backtracking.+--+module Data.Binary.Parser+    (+    -- * Running parsers+      Parser+    , parseOnly+    , parseLazy+    , parseDetail+    , parseDetailLazy+    , parse+    -- * Decoder conversion+    , maybeDecoder+    , eitherDecoder+    -- * Combinators+    , (<?>)+    , endOfInput+    , option+    , eitherP+    , match+    , many'+    , some'+    , sepBy+    , sepBy'+    , sepBy1+    , sepBy1'+    , manyTill+    , manyTill'+    , skipMany+    , skipMany1+    -- * Re-exports+    , module Data.Binary.Get+    , module Data.Binary.Parser.Word8+    , module Data.Binary.Parser.Numeric+    ) where++import           Control.Applicative+import           Control.Monad+import           Data.Binary.Get+import qualified Data.Binary.Get.Internal   as I+import           Data.Binary.Parser.Numeric+import           Data.Binary.Parser.Word8+import qualified Data.ByteString            as B+import qualified Data.ByteString.Lazy       as L+import qualified Data.ByteString.Lazy.Internal as L (ByteString(..))++--------------------------------------------------------------------------------++-- | Alias to 'Get' for attoparsec compatibility.+type Parser a = Get a++-- | Run a parser on 'B.ByteString'.+--+-- This function does not force a parser to consume all of its input.+-- Instead, any residual input will be discarded.  To force a parser+-- to consume all of its input, use something like this:+--+-- @parseOnly (myParser <* endOfInput)@+--+parseOnly :: Get a -> B.ByteString -> Either String a+parseOnly g bs =+    case pushEndOfInput (parse g bs) of+        Fail _ _ err -> Left err+        Done _ _ a -> Right a+        _ -> error "parseOnly: impossible error!"+{-# INLINE parseOnly #-}+++-- | Similar to 'parseOnly', but run a parser on lazy 'L.ByteString'.+--+parseLazy :: Get a -> L.ByteString -> Either String a+parseLazy g (L.Chunk bs lbs) =+    case pushEndOfInput (pushChunks (parse g bs) lbs) of+        Fail _ _ err -> Left err+        Done _ _ a -> Right a+        _ -> error "parseOnly: impossible error!"+parseLazy g L.Empty =+    case pushEndOfInput (parse g B.empty) of+        Fail _ _ err -> Left err+        Done _ _ a -> Right a+        _ -> error "parseOnly: impossible error!"+{-# INLINE parseLazy #-}++-- | Run a parser on 'B.ByteString'.+--+-- This function return full parsing results: the rest of input, stop offest and fail+-- message or parsing result.+--+-- /Since: 0.2.1.0/+--+parseDetail :: Get a+            -> B.ByteString+            -> Either (B.ByteString, ByteOffset, String) (B.ByteString, ByteOffset, a)+parseDetail g bs =+    case pushEndOfInput (parse g bs) of+        Fail rest offset err -> Left (rest, offset, err)+        Done rest offset a   -> Right (rest, offset, a)+        _ -> error "parseOnly: impossible error!"+{-# INLINE parseDetail #-}++-- | Similar to 'parseDetail', but run a parser on lazy 'L.ByteString'.+--+-- /Since: 0.2.1.0/+--+parseDetailLazy :: Get a+                -> L.ByteString+                -> Either (B.ByteString, ByteOffset, String) (B.ByteString, ByteOffset, a)+parseDetailLazy g (L.Chunk bs lbs) =+    case pushEndOfInput (pushChunks (parse g bs) lbs) of+        Fail rest offset err -> Left (rest, offset, err)+        Done rest offset a   -> Right (rest, offset, a)+        _ -> error "parseOnly: impossible error!"+parseDetailLazy g L.Empty =+    case pushEndOfInput (parse g B.empty) of+        Fail rest offset err -> Left (rest, offset, err)+        Done rest offset a   -> Right (rest, offset, a)+        _ -> error "parseOnly: impossible error!"+{-# INLINE parseDetailLazy #-}++-- | Run a 'Get' monad. See 'Decoder' for what to do next, like providing+-- input, handling decoding errors and to get the output value.+--+-- This's faster than 'runGetIncremental' becuase it provides an initial chunk rather+-- than feeding 'B.empty' and waiting for chunks, this overhead is noticeable when you're+-- running small getters over short 'ByteString' s.+--+-- /Since: 0.2.1.0/+--+parse :: Get a -> B.ByteString -> Decoder a+parse g bs = calculateOffset (loop (I.runCont g bs I.Done)) (fromIntegral $ B.length bs)+  where+    calculateOffset r !acc = case r of+        I.Done inp a -> Done inp (acc - fromIntegral (B.length inp)) a+        I.Fail inp s -> Fail inp (acc - fromIntegral (B.length inp)) s+        I.Partial k -> Partial $ \ms -> case ms of+                Nothing -> calculateOffset (k Nothing) acc+                Just i -> calculateOffset (k ms) (acc + fromIntegral (B.length i))+        I.BytesRead unused k -> calculateOffset (k $! (acc - unused)) acc++    loop r = case r of+        I.Partial k -> I.Partial $ \ms -> case ms of Just _ -> loop (k ms)+                                                     Nothing -> completeLoop (k ms)+        I.BytesRead n k -> I.BytesRead n (loop . k)+        I.Done _ _ -> r+        I.Fail _ _ -> r++    completeLoop r = case r of+        I.Partial k -> completeLoop (k Nothing)+        I.BytesRead n k -> I.BytesRead n (completeLoop . k)+        I.Fail _ _ -> r+        I.Done _ _ -> r++--------------------------------------------------------------------------------++-- | Convert a 'Decoder' value to a 'Maybe' value. A 'Partial' result+-- is treated as failure.+--+-- /Since: 0.2.3.0/+--+maybeDecoder :: Decoder r -> Maybe r+maybeDecoder (Done _ _ r) = Just r+maybeDecoder _            = Nothing+{-# INLINE maybeDecoder #-}++-- | Convert a 'Decoder' value to an 'Either' value. A 'Partial'+-- result is treated as failure.+--+-- /Since: 0.2.3.0/+--+eitherDecoder :: Decoder r -> Either String r+eitherDecoder (Done _ _ r)   = Right r+eitherDecoder (Fail _ _ msg) = Left msg+eitherDecoder _              = Left "Decoder: incomplete input"+{-# INLINE eitherDecoder #-}++--------------------------------------------------------------------------------++-- | Name the parser, in case failure occurs.+(<?>) :: Get a -> String -> Get a+(<?>) = flip label+infix 0 <?>+{-# INLINE (<?>) #-}++-- | Match only if all input has been consumed.+endOfInput :: Get ()+endOfInput = do+    e <- isEmpty+    unless e (fail "endOfInput")+{-# INLINE endOfInput #-}++-- | @option x p@ tries to apply action @p@. If @p@ fails without+-- consuming input, it returns the value @x@, otherwise the value+-- returned by @p@.+--+-- > priority  = option 0 (digitToInt <$> digit)+option :: Alternative f => a -> f a -> f a+option x p = p <|> pure x+{-# SPECIALIZE option :: a -> Get a -> Get a #-}++-- | Combine two alternatives.+eitherP :: (Alternative f) => f a -> f b -> f (Either a b)+eitherP a b = (Left <$> a) <|> (Right <$> b)+{-# INLINE eitherP #-}++-- | Return both the result of a parse and the portion of the input+-- that was consumed while it was being parsed.+match :: Get a -> Get (B.ByteString, a)+match p = do+    pos1 <- bytesRead+    (x, pos2) <- lookAhead $ (,) <$> p <*> bytesRead+    (,) <$> (getByteString . fromIntegral) (pos2 - pos1) <*> pure x+{-# INLINE match #-}++-- | A version of 'liftM2' that is strict in the result of its first+-- action.+liftM2' :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c+liftM2' f a b = do+  !x <- a+  y <- b+  return (f x y)+{-# INLINE liftM2' #-}++-- | @many' p@ applies the action @p@ /zero/ or more times. Returns a+-- list of the returned values of @p@. The value returned by @p@ is+-- forced to WHNF.+--+-- >  word  = many' letter+many' :: (MonadPlus m) => m a -> m [a]+many' p = many_p+  where many_p = some_p `mplus` return []+        some_p = liftM2' (:) p many_p+{-# INLINE many' #-}++-- | @some' p@ applies the action @p@ /one/ or more times. Returns a+-- list of the returned values of @p@. The value returned by @p@ is+-- forced to WHNF.+--+-- >  word  = some' letter+some' :: (MonadPlus m) => m a -> m [a]+some' p = liftM2' (:) p (many' p)+{-# INLINE some' #-}++-- | @sepBy p sep@ applies /zero/ or more occurrences of @p@, separated+-- by @sep@. Returns a list of the values returned by @p@.+--+-- > commaSep p  = p `sepBy` (char ',')+sepBy :: Alternative f => f a -> f s -> f [a]+sepBy p s = liftA2 (:) p ((s *> sepBy1 p s) <|> pure []) <|> pure []+{-# SPECIALIZE sepBy :: Get a -> Get s -> Get [a] #-}++-- | @sepBy' p sep@ applies /zero/ or more occurrences of @p@, separated+-- by @sep@. Returns a list of the values returned by @p@. The value+-- returned by @p@ is forced to WHNF.+--+-- > commaSep p  = p `sepBy'` (char ',')+sepBy' :: (MonadPlus m) => m a -> m s -> m [a]+sepBy' p s = go `mplus` return []+  where go = liftM2' (:) p ((s >> sepBy1' p s) `mplus` return [])+{-# SPECIALIZE sepBy' :: Get a -> Get s -> Get [a] #-}++-- | @sepBy1 p sep@ applies /one/ or more occurrences of @p@, separated+-- by @sep@. Returns a list of the values returned by @p@.+--+-- > commaSep p  = p `sepBy1` (char ',')+sepBy1 :: Alternative f => f a -> f s -> f [a]+sepBy1 p s = go+    where go = liftA2 (:) p ((s *> go) <|> pure [])+{-# SPECIALIZE sepBy1 :: Get a -> Get s -> Get [a] #-}++-- | @sepBy1' p sep@ applies /one/ or more occurrences of @p@, separated+-- by @sep@. Returns a list of the values returned by @p@. The value+-- returned by @p@ is forced to WHNF.+--+-- > commaSep p  = p `sepBy1'` (char ',')+sepBy1' :: (MonadPlus m) => m a -> m s -> m [a]+sepBy1' p s = go+    where go = liftM2' (:) p ((s >> go) `mplus` return [])+{-# SPECIALIZE sepBy1' :: Get a -> Get s -> Get [a] #-}++-- | @manyTill p end@ applies action @p@ /zero/ or more times until+-- action @end@ succeeds, and returns the list of values returned by+-- @p@.  This can be used to scan comments:+--+-- >  simpleComment   = string "<!--" *> manyTill anyChar (string "-->")+--+-- (Note the overlapping parsers @anyChar@ and @string \"-->\"@.+-- While this will work, it is not very efficient, as it will cause a+-- lot of backtracking.)+manyTill :: Alternative f => f a -> f b -> f [a]+manyTill p end = go+    where go = (end *> pure []) <|> liftA2 (:) p go+{-# SPECIALIZE manyTill :: Get a -> Get b -> Get [a] #-}++-- | @manyTill' p end@ applies action @p@ /zero/ or more times until+-- action @end@ succeeds, and returns the list of values returned by+-- @p@.  This can be used to scan comments:+--+-- >  simpleComment   = string "<!--" *> manyTill' anyChar (string "-->")+--+-- (Note the overlapping parsers @anyChar@ and @string \"-->\"@.+-- While this will work, it is not very efficient, as it will cause a+-- lot of backtracking.)+--+-- The value returned by @p@ is forced to WHNF.+manyTill' :: (MonadPlus m) => m a -> m b -> m [a]+manyTill' p end = go+    where go = (end >> return []) `mplus` liftM2' (:) p go+{-# SPECIALIZE manyTill' :: Get a -> Get b -> Get [a] #-}++-- | Skip zero or more instances of an action.+skipMany :: Alternative f => f a -> f ()+skipMany p = go+    where go = (p *> go) <|> pure ()+{-# SPECIALIZE skipMany :: Get a -> Get () #-}++-- | Skip one or more instances of an action.+skipMany1 :: Alternative f => f a -> f ()+skipMany1 p = p *> skipMany p+{-# SPECIALIZE skipMany1 :: Get a -> Get () #-}
+ src/Data/Binary/Parser/Numeric.hs view
@@ -0,0 +1,165 @@+{-# LANGUAGE CPP          #-}+-- |+-- Module      :  Data.Binary.Parser.Numeric+-- Copyright   :  Bryan O'Sullivan 2007-2015, Winterland 2016+-- License     :  BSD3+--+-- Maintainer  :  drkoster@qq.com+-- Stability   :  experimental+-- Portability :  unknown+--+-- Simple, efficient combinator parsing for numeric values.+--+module Data.Binary.Parser.Numeric where++import           Control.Applicative+import           Control.Monad+import           Data.Binary.Get.Internal+import qualified Data.Binary.Parser.Word8     as W+import qualified Data.ByteString              as B+import qualified Data.ByteString.Lex.Integral as LexInt+import           Data.Int+import           Data.Scientific              (Scientific (..))+import qualified Data.Scientific              as Sci+import           Data.Word++#define  MINUS    45+#define  PLUS     43+#define  LITTLE_E 101+#define  BIG_E    69+#define  DOT      46++-- | Parse and decode an unsigned hexadecimal number.  The hex digits+-- @\'a\'@ through @\'f\'@ may be upper or lower case.+--+-- This parser does not accept a leading @\"0x\"@ string.+--+hexadecimal :: (Integral a) => Get a+hexadecimal = do+    bs <- W.takeWhile1 W.isHexDigit+    case LexInt.readHexadecimal bs of+        Just (x, _) -> return x+        Nothing -> fail "hexadecimal: impossible"+{-# SPECIALISE hexadecimal :: Get Int #-}+{-# SPECIALISE hexadecimal :: Get Int8 #-}+{-# SPECIALISE hexadecimal :: Get Int16 #-}+{-# SPECIALISE hexadecimal :: Get Int32 #-}+{-# SPECIALISE hexadecimal :: Get Int64 #-}+{-# SPECIALISE hexadecimal :: Get Integer #-}+{-# SPECIALISE hexadecimal :: Get Word #-}+{-# SPECIALISE hexadecimal :: Get Word8 #-}+{-# SPECIALISE hexadecimal :: Get Word16 #-}+{-# SPECIALISE hexadecimal :: Get Word32 #-}+{-# SPECIALISE hexadecimal :: Get Word64 #-}++-- | Parse and decode an unsigned decimal number.+--+decimal :: Integral a => Get a+decimal = do+    bs <- W.takeWhile1 W.isDigit+    return $! LexInt.readDecimal_ bs+{-# SPECIALISE decimal :: Get Int #-}+{-# SPECIALISE decimal :: Get Int8 #-}+{-# SPECIALISE decimal :: Get Int16 #-}+{-# SPECIALISE decimal :: Get Int32 #-}+{-# SPECIALISE decimal :: Get Int64 #-}+{-# SPECIALISE decimal :: Get Integer #-}+{-# SPECIALISE decimal :: Get Word #-}+{-# SPECIALISE decimal :: Get Word8 #-}+{-# SPECIALISE decimal :: Get Word16 #-}+{-# SPECIALISE decimal :: Get Word32 #-}+{-# SPECIALISE decimal :: Get Word64 #-}++-- | Parse a number with an optional leading @\'+\'@ or @\'-\'@ sign+-- character.+--+signed :: Num a => Get a -> Get a+signed p = do+    w <- W.peek+    if w == MINUS+        then W.skipN 1 >> negate <$> p+        else if w == PLUS then W.skipN 1 >> p else p+{-# SPECIALISE signed :: Get Int -> Get Int #-}+{-# SPECIALISE signed :: Get Int8 -> Get Int8 #-}+{-# SPECIALISE signed :: Get Int16 -> Get Int16 #-}+{-# SPECIALISE signed :: Get Int32 -> Get Int32 #-}+{-# SPECIALISE signed :: Get Int64 -> Get Int64 #-}+{-# SPECIALISE signed :: Get Integer -> Get Integer #-}++-- | Parse a rational number.+--+-- The syntax accepted by this parser is the same as for 'double'.+--+-- /Note/: this parser is not safe for use with inputs from untrusted+-- sources.  An input with a suitably large exponent such as+-- @"1e1000000000"@ will cause a huge 'Integer' to be allocated,+-- resulting in what is effectively a denial-of-service attack.+--+-- In most cases, it is better to use 'double' or 'scientific'+-- instead.+--+rational :: Fractional a => Get a+rational = scientifically realToFrac+{-# SPECIALIZE rational :: Get Double #-}+{-# SPECIALIZE rational :: Get Float #-}+{-# SPECIALIZE rational :: Get Rational #-}+{-# SPECIALIZE rational :: Get Scientific #-}++-- | Parse a rational number and round to 'Double'.+--+-- This parser accepts an optional leading sign character, followed by+-- at least one decimal digit.  The syntax similar to that accepted by+-- the 'read' function, with the exception that a trailing @\'.\'@ or+-- @\'e\'@ /not/ followed by a number is not consumed.+--+-- Examples with behaviour identical to 'read':+--+-- >parseOnly double "3"     == Right ("",1,3.0)+-- >parseOnly double "3.1"   == Right ("",3,3.1)+-- >parseOnly double "3e4"   == Right ("",3,30000.0)+-- >parseOnly double "3.1e4" == Right ("",5,31000.0)+--+-- >parseOnly double ".3"    == Left (".3",0,"takeWhile1")+-- >parseOnly double "e3"    == Left ("e3",0,"takeWhile1")+--+-- Examples of differences from 'read':+--+-- >parseOnly double "3.foo" == Right (".foo",1,3.0)+-- >parseOnly double "3e"    == Right ("e",1,3.0)+--+-- This function does not accept string representations of \"NaN\" or+-- \"Infinity\".+--+double :: Get Double+double = scientifically Sci.toRealFloat++-- | Parse a scientific number.+--+-- The syntax accepted by this parser is the same as for 'double'.+--+scientific :: Get Scientific+scientific = scientifically id++-- | Parse a scientific number and convert to result using a user supply function.+--+-- The syntax accepted by this parser is the same as for 'double'.+--+scientifically :: (Scientific -> a) -> Get a+scientifically h = do+    sign <- W.peek+    when (sign == PLUS || sign == MINUS) (W.skipN 1)+    intPart <- decimal+    sci <- (do fracDigits <- W.word8 DOT >> W.takeWhile1 W.isDigit+               let e' = B.length fracDigits+                   intPart' = intPart * (10 ^ e')+                   fracPart = LexInt.readDecimal_ fracDigits+               parseE (intPart' + fracPart) e'+           ) <|> (parseE intPart 0)++    if sign /= MINUS then return $! h sci else return $! h (negate sci)+  where+    parseE c e =+        (do _ <- W.satisfy (\w -> w ==  LITTLE_E || w == BIG_E)+            (Sci.scientific c . (subtract e) <$> signed decimal)) <|> return (Sci.scientific c (negate e))+    {-# INLINE parseE #-}+{-# INLINE scientifically #-}
+ src/Data/Binary/Parser/Word8.hs view
@@ -0,0 +1,314 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP          #-}+-- |+-- Module      :  Data.Binary.Parser.Word8+-- Copyright   :  Bryan O'Sullivan 2007-2015, Winterland 2016+-- License     :  BSD3+--+-- Maintainer  :  drkoster@qq.com+-- Stability   :  experimental+-- Portability :  unknown+--+-- Simple, efficient combinator parsing for 'B.ByteString' strings.+--+module Data.Binary.Parser.Word8 where++import           Control.Applicative+import           Control.Monad+import           Data.Binary.Get+import           Data.Binary.Get.Internal+import           Data.ByteString          (ByteString)+import qualified Data.ByteString          as B+import           Data.ByteString.Internal (ByteString (..))+import qualified Data.ByteString.Unsafe   as B+import           Data.Word+import           Foreign.ForeignPtr       (withForeignPtr)+import           Foreign.Ptr              (minusPtr, plusPtr)+import qualified Foreign.Storable         as Storable (Storable (peek))+import           Prelude                  hiding (takeWhile)++#if MIN_VERSION_bytestring(0,10,6)+import           Data.ByteString.Internal (accursedUnutterablePerformIO)+#else+import           Data.ByteString.Internal (inlinePerformIO)++{-# INLINE accursedUnutterablePerformIO #-}+-- | You must be truly desperate to come to me for help.+accursedUnutterablePerformIO :: IO a -> a+accursedUnutterablePerformIO = inlinePerformIO+#endif++--------------------------------------------------------------------------------++-- | Match any byte, to perform lookahead. Returns 'Nothing' if end of+-- input has been reached. Does not consume any input.+--+peekMaybe :: Get (Maybe Word8)+peekMaybe = do+    e <- isEmpty+    if e then return Nothing+         else Just <$> peek+{-# INLINE peekMaybe #-}++-- | Match any byte, to perform lookahead.  Does not consume any+-- input, but will fail if end of input has been reached.+--+peek :: Get Word8+peek = do+    ensureN 1+    bs <- get+    return (B.unsafeHead bs)+{-# INLINE peek #-}++-- | The parser @satisfy p@ succeeds for any byte for which the+-- predicate @p@ returns 'True'. Returns the byte that is actually+-- parsed.+--+-- >digit = satisfy isDigit+-- >    where isDigit w = w >= 48 && w <= 57+--+satisfy :: (Word8 -> Bool) -> Get Word8+satisfy p = do+    ensureN 1+    bs <- get+    let w = B.unsafeHead bs+    if p w then put (B.unsafeTail bs) >> return w+           else fail "satisfy"+{-# INLINE satisfy #-}++-- | The parser @satisfyWith f p@ transforms a byte, and succeeds if+-- the predicate @p@ returns 'True' on the transformed value. The+-- parser returns the transformed byte that was parsed.+--+satisfyWith :: (Word8 -> a) -> (a -> Bool) -> Get a+satisfyWith f p = do+    ensureN 1+    bs <- get+    let w = B.unsafeHead bs+        r = f w+    if p r then put (B.unsafeTail bs) >> return r+           else fail "satisfyWith"+{-# INLINE satisfyWith #-}++-- | Match a specific byte.+--+word8 :: Word8 -> Get ()+word8 c = do+    ensureN 1+    bs <- get+    let w = B.unsafeHead bs+    if c == w then put (B.unsafeTail bs)+              else fail "word8"+{-# INLINE word8 #-}++-- | Match any byte.+--+anyWord8 :: Get Word8+anyWord8 = getWord8+{-# INLINE anyWord8 #-}++-- | The parser @skipWord8 p@ succeeds for any byte for which the predicate @p@ returns 'True'.+--+skipWord8 :: (Word8 -> Bool) -> Get ()+skipWord8 p = do+    ensureN 1+    bs <- get+    let w = B.unsafeHead bs+    if p w then put (B.unsafeTail bs)+              else fail "skip"+{-# INLINE skipWord8 #-}++--------------------------------------------------------------------------------++-- | This is a faster version of 'skip' for small N (smaller than chunk size).+--+skipN :: Int -> Get ()+skipN n = do+    bs <- get+    let l = B.length bs+    if l > n then put (B.unsafeDrop n bs)+             else skip n+{-# INLINE skipN #-}++-- | Consume input as long as the predicate returns 'False' or reach the end of input,+-- and return the consumed input.+--+takeTill :: (Word8 -> Bool) -> Get ByteString+takeTill p = do+    bs <- get+    let (want, rest) = B.break p bs+    put rest+    if B.null rest then B.concat . reverse <$> go [want] else return want+  where+    go acc = do+        e <- isEmpty -- isEmpty will draw input here+        if e+        then return acc+        else do+            bs <- get+            let (want, rest) = B.break p bs+                acc' = want : acc+            put rest+            if B.null rest then go acc' else return acc'+{-# INLINE takeTill #-}++-- | Consume input as long as the predicate returns 'True' or reach the end of input,+-- and return the consumed input.+--+takeWhile :: (Word8 -> Bool) -> Get ByteString+takeWhile p = do+    bs <- get+    let (want, rest) = B.span p bs+    put rest+    if B.null rest then B.concat . reverse <$> go [want] else return want+  where+    go acc = do+        e <- isEmpty+        if e+        then return acc+        else do+            bs <- get+            let (want, rest) = B.span p bs+                acc' = want : acc+            put rest+            if B.null rest then go acc' else return acc'+{-# INLINE takeWhile #-}++-- | Similar to 'takeWhile', but requires the predicate to succeed on at least one byte+-- of input: it will fail if the predicate never returns 'True' or reach the end of input+--+takeWhile1 :: (Word8 -> Bool) -> Get ByteString+takeWhile1 p = do+    bs <- takeWhile p+    if B.null bs then fail "takeWhile1" else return bs+{-# INLINE takeWhile1 #-}++-- | Skip past input for as long as the predicate returns 'True'.+--+skipWhile :: (Word8 -> Bool) -> Get ()+skipWhile p = do+    bs <- get+    let rest = B.dropWhile p bs+    put rest+    when (B.null rest) go+  where+    go = do+        e <- isEmpty+        unless e $ do+            bs <- get+            let rest = B.dropWhile p bs+            put rest+            when (B.null rest) go+{-# INLINE skipWhile #-}++-- | Skip over white space using 'isSpace'.+--+skipSpaces :: Get ()+skipSpaces = skipWhile isSpace+{-# INLINE skipSpaces #-}++-- | @string s@ parses a sequence of bytes that identically match @s@.+--+string :: ByteString -> Get ()+string bs = do+    let l = B.length bs+    bs' <- get+    if l <= B.length bs'              -- current chunk is enough+    then if B.unsafeTake l bs' == bs+        then put (B.unsafeDrop l bs')+        else fail "string"+    else do+        ensureN l+        bs'' <- get+        if B.unsafeTake l bs'' == bs+        then put (B.unsafeDrop l bs'')+        else fail "string"+{-# INLINE string #-}++-- | A stateful scanner.  The predicate consumes and transforms a+-- state argument, and each transformed state is passed to successive+-- invocations of the predicate on each byte of the input until one+-- returns 'Nothing' or the input ends.+--+-- This parser does not fail.  It will return an empty string if the+-- predicate returns 'Nothing' on the first byte of input.+--+scan :: s -> (s -> Word8 -> Maybe s) -> Get ByteString+scan s0 consume = withInputChunks s0 consume' B.concat (return . B.concat)+  where+    consume' s1 (PS fp off len) = accursedUnutterablePerformIO $+        withForeignPtr fp $ \ptr0 -> do+            let start = ptr0 `plusPtr` off+                end   = start `plusPtr` len+            go fp off start end start s1+    go fp off start end ptr !s+        | ptr < end = do+            w <- Storable.peek ptr+            case consume s w of+                Just s' -> go fp off start end (ptr `plusPtr` 1) s'+                _       -> do+                    let !len1 = ptr `minusPtr` start+                        !off2 = off + len1+                        !len2 = end `minusPtr` ptr+                    return (Right (PS fp off len1, PS fp off2 len2))+        | otherwise = return (Left s)+{-# INLINE scan #-}++-- | Similar to 'scan', but working on 'ByteString' chunks, The predicate+-- consumes a 'ByteString' chunk and transforms a state argument,+-- and each transformed state is passed to successive invocations of+-- the predicate on each chunk of the input until one chunk got splited to+-- @Right (ByteString, ByteString)@ or the input ends.+--+scanChunks :: s -> Consume s -> Get ByteString+scanChunks s consume = withInputChunks s consume B.concat (return . B.concat)+{-# INLINE scanChunks #-}++--------------------------------------------------------------------------------++-- | Fast 'Word8' predicate for matching ASCII space characters+--+-- >isSpace w = w == 32 || w - 9 <= 4+--+isSpace :: Word8 -> Bool+isSpace w = w == 32 || w - 9 <= 4+{-# INLINE isSpace #-}++-- | Decimal digit predicate.+--+isDigit :: Word8 -> Bool+isDigit w = w - 48 <= 9+{-# INLINE isDigit #-}++-- | Hex digit predicate.+--+isHexDigit :: Word8 -> Bool+isHexDigit w = (w >= 48 && w <= 57) || (w >= 97 && w <= 102) || (w >= 65 && w <= 70)+{-# INLINE isHexDigit #-}++-- | A predicate that matches either a space @\' \'@ or horizontal tab+-- @\'\\t\'@ character.+--+isHorizontalSpace :: Word8 -> Bool+isHorizontalSpace w = w == 32 || w == 9+{-# INLINE isHorizontalSpace #-}++-- | A predicate that matches either a carriage return @\'\\r\'@ or+-- newline @\'\\n\'@ character.+--+isEndOfLine :: Word8 -> Bool+isEndOfLine w = w == 13 || w == 10+{-# INLINE isEndOfLine #-}++--------------------------------------------------------------------------------++-- | Match either a single newline byte @\'\\n\'@, or a carriage+-- return followed by a newline byte @\"\\r\\n\"@.+endOfLine :: Get ()+endOfLine = do+    w <- getWord8+    case w of+        10 -> return ()+        13 -> word8 10+        _  -> fail "endOfLine"+{-# INLINE endOfLine #-}
+ src/Data/Connection.hs view
@@ -0,0 +1,42 @@+module Data.Connection where++import qualified Data.ByteString           as B+import qualified Data.ByteString.Lazy.Internal as L+import qualified System.IO.Streams         as S++-- | A simple connection abstraction.+--+-- 'Connection' s from this package are supposed to have following properties:+--+--  * 'S.InputStream' is choosen to simplify streaming processing.+--  You can easily push back some data with 'S.unRead',+--  reading 'S.InputStream' will block until GHC IO manager find data is ready, for example:+--  @'S.readExactly' 1024@ will block until at least 1024 bytes are available.+--+--  * The type @'L.ByteString' -> 'IO' ()@ is choosen because it worked well with haskell's builder infrastructure.+--  <http://hackage.haskell.org/package/network-2.6.2.1/docs/Network-Socket-ByteString.html#g:2 vector-IO>+--  is used automatically when there's more than one chunk to send to save system call.+--+--  * 'connExtraInfo' field store extra data about the connection, 'N.SockAddr' for example.+--  You can also use this field as a type tag to distinguish different type of connection.+--+--  * 'close' should close connection resource, thus the 'Connection' shouldn't be used anymore+--  after 'close' is called.+--+--  * You should make sure there's no pending recv/send before you 'close' a 'Connection'.+--  That means either you call 'close' in the same thread you recv/send, or use async exception+--  to terminate recv/send thread before call 'close' in other thread(such as a reaper thread).+--  Otherwise you may run into+--  <https://mail.haskell.org/pipermail/haskell-cafe/2014-September/115823.html race-connections>.+--+--  * Exception or closed by other peer during recv/send will NOT close underline socket,+--  you should always use 'close' with 'E.bracket' to ensure safety.+--+--  @since 1.0+--+data Connection a = Connection+    { source        :: {-# UNPACK #-} !(S.InputStream B.ByteString)   -- ^ receive data as 'S.InputStream'+    , send          :: L.ByteString -> IO ()                          -- ^ send data with connection+    , close         :: IO ()                                          -- ^ close connection+    , connExtraInfo :: a                                              -- ^ extra info+    }
+ src/Data/Int/Int24.hs view
@@ -0,0 +1,250 @@+{-# OPTIONS_GHC -Wno-inline-rule-shadowing #-}++{-# LANGUAGE BangPatterns      #-}+{-# LANGUAGE CPP               #-}+{-# LANGUAGE MagicHash         #-}+{-# LANGUAGE NoImplicitPrelude #-}++#if !MIN_VERSION_base(4,8,0)+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE StandaloneDeriving #-}+#endif++-----------------------------------------------------------------------------+-- |+-- Module      :  Data.Int.Int24+-- Copyright   :  (c) The University of Glasgow 1997-2002+-- License     :  see src/Data/LICENSE+--+-- Stability   :  experimental+-- Portability :  non-portable (GHC Extensions)+--+-- The 24 bit integral datatype, 'Int24'.+--+-----------------------------------------------------------------------------++module Data.Int.Int24 (+  -- * Int24 type+  Int24(..)+  -- * Internal helper+  , narrow24Int#+  ) where++import           Data.Bits+import           Data.Data+import           Data.Maybe+import           Data.Word.Word24+import           Foreign.Storable++import           GHC.Arr+import           GHC.Base+import           GHC.Enum+import           GHC.Int.Compat+import           GHC.Integer (smallInteger, integerToInt)+import           GHC.Num hiding (integerToInt)+import           GHC.Ptr+import           GHC.Read+import           GHC.Real+import           GHC.Show+import           GHC.Word.Compat++import           Control.DeepSeq++#if !MIN_VERSION_base(4,8,0)+import           Data.Typeable+#endif++------------------------------------------------------------------------++-- Int24 is represented in the same way as Int. Operations may assume+-- and must ensure that it holds only values from its logical range.++-- | 24-bit signed integer type+data Int24 = I24# Int# deriving (Eq, Ord)++#if !MIN_VERSION_base(4,8,0)+deriving instance Typeable Int24+#endif++instance NFData Int24  where rnf !_ = ()++int24Type :: DataType+int24Type = mkIntType "Data.Word.Int24.Int24"++instance Data Int24 where+  toConstr x = mkIntegralConstr int24Type x+  gunfold _ z c = case constrRep c of+                    (IntConstr x) -> z (fromIntegral x)+                    _ -> error $ "Data.Data.gunfold: Constructor " ++ show c+                                 ++ " is not of type Int24."+  dataTypeOf _ = int24Type++-- the narrowings are primops in GHC; I don't have that luxury.+-- if the 24th bit (from right) is on, the value is negative, so+-- fill the uppermost bits with 1s.  Otherwise clear them to 0s.+narrow24Int# :: Int# -> Int#+narrow24Int# x# = if isTrue# ((x'# `and#` mask#) `eqWord#` mask#)+    then word2Int# (x'# `or#`  int2Word# m1#)+    else word2Int# (x'# `and#` int2Word# m2#)+  where+    !x'#   = int2Word# x#+    !mask# = int2Word# 0x00800000#+    !(I# m1#) = -8388608+    !(I# m2#) = 16777215++instance Show Int24 where+    showsPrec p x = showsPrec p (fromIntegral x :: Int)++instance Num Int24 where+    (I24# x#) + (I24# y#)  = I24# (narrow24Int# (x# +# y#))+    (I24# x#) - (I24# y#)  = I24# (narrow24Int# (x# -# y#))+    (I24# x#) * (I24# y#)  = I24# (narrow24Int# (x# *# y#))+    negate (I24# x#)       = I24# (narrow24Int# (negateInt# x#))+    abs x | x >= 0         = x+          | otherwise      = negate x+    signum x | x > 0       = 1+    signum 0               = 0+    signum _               = -1+    fromInteger i          = I24# (narrow24Int# (integerToInt i))++instance Real Int24 where+    toRational x = toInteger x % 1++instance Enum Int24 where+    succ x+        | x /= maxBound = x + 1+        | otherwise     = succError "Int24"+    pred x+        | x /= minBound = x - 1+        | otherwise     = predError "Int24"+    toEnum i@(I# i#)+        | i >= fromIntegral (minBound::Int24) && i <= fromIntegral (maxBound::Int24)+                        = I24# i#+        | otherwise     = toEnumError "Int24" i (minBound::Int24, maxBound::Int24)+    fromEnum (I24# x#)  = I# x#+    enumFrom            = boundedEnumFrom+    enumFromThen        = boundedEnumFromThen++instance Integral Int24 where+    quot    x@(I24# x#) y@(I24# y#)+        | y == 0                     = divZeroError+        | x == minBound && y == (-1) = overflowError+        | otherwise                  = I24# (narrow24Int# (x# `quotInt#` y#))+    rem     x@(I24# x#) y@(I24# y#)+        | y == 0                     = divZeroError+        | x == minBound && y == (-1) = overflowError+        | otherwise                  = I24# (narrow24Int# (x# `remInt#` y#))+    div     x@(I24# x#) y@(I24# y#)+        | y == 0                     = divZeroError+        | x == minBound && y == (-1) = overflowError+        | otherwise                  = I24# (narrow24Int# (x# `divInt#` y#))+    mod     x@(I24# x#) y@(I24# y#)+        | y == 0                     = divZeroError+        | x == minBound && y == (-1) = overflowError+        | otherwise                  = I24# (narrow24Int# (x# `modInt#` y#))+    quotRem x@(I24# x#) y@(I24# y#)+        | y == 0                     = divZeroError+        | x == minBound && y == (-1) = overflowError+        | otherwise                  = (I24# (narrow24Int# (x# `quotInt#` y#)),+                                        I24# (narrow24Int# (x# `remInt#` y#)))+    divMod  x@(I24# x#) y@(I24# y#)+        | y == 0                     = divZeroError+        | x == minBound && y == (-1) = overflowError+        | otherwise                  = (I24# (narrow24Int# (x# `divInt#` y#)),+                                        I24# (narrow24Int# (x# `modInt#` y#)))+    toInteger (I24# x#)              = smallInteger x#++instance Bounded Int24 where+    minBound = -0x800000+    maxBound =  0x7FFFFF++instance Ix Int24 where+    range (m,n)         = [m..n]+    unsafeIndex (m,_) i = fromIntegral i - fromIntegral m+    inRange (m,n) i     = m <= i && i <= n++instance Read Int24 where+    readsPrec p s = [(fromIntegral (x::Int), r) | (x, r) <- readsPrec p s]++instance Bits Int24 where+    {-# INLINE shift #-}+    {-# INLINE bit #-}+    {-# INLINE testBit #-}++    (I24# x#) .&.   (I24# y#)  = I24# (word2Int# (int2Word# x# `and#` int2Word# y#))+    (I24# x#) .|.   (I24# y#)  = I24# (word2Int# (int2Word# x# `or#`  int2Word# y#))+    (I24# x#) `xor` (I24# y#)  = I24# (word2Int# (int2Word# x# `xor#` int2Word# y#))+    complement (I24# x#)       = I24# (word2Int# (not# (int2Word# x#)))+    (I24# x#) `shift` (I# i#)+        | isTrue# (i# >=# 0#)  = I24# (narrow24Int# (x# `iShiftL#` i#))+        | otherwise            = I24# (x# `iShiftRA#` negateInt# i#)+    (I24# x#) `shiftL`       (I# i#) = I24# (narrow24Int# (x# `iShiftL#` i#))+    (I24# x#) `unsafeShiftL` (I# i#) = I24# (narrow24Int# (x# `uncheckedIShiftL#` i#))+    (I24# x#) `shiftR`       (I# i#) = I24# (x# `iShiftRA#` i#)+    (I24# x#) `unsafeShiftR` (I# i#) = I24# (x# `uncheckedIShiftRA#` i#)+    (I24# x#) `rotate` i+        | isTrue# (i'# ==# 0#) = I24# x#+        | otherwise = I24# (narrow24Int# (word2Int# ((x'# `uncheckedShiftL#` i'#) `or#`+                                         (x'# `uncheckedShiftRL#` (24# -# i'#)))))+      where+        !x'# = narrow24Word# (int2Word# x#)+        !(I# i'#) = i `mod` 24+    bitSizeMaybe i             = Just (finiteBitSize i)+    bitSize                    = finiteBitSize+    isSigned _                 = True+    popCount (I24# x#)         = I# (word2Int# (popCnt24# (int2Word# x#)))+    bit                        = bitDefault+    testBit                    = testBitDefault++instance FiniteBits Int24 where+    finiteBitSize _ = 24+#if MIN_VERSION_base(4,8,0)+    countLeadingZeros  (I24# x#) = I# (word2Int# (clz24# (int2Word# x#)))+    countTrailingZeros (I24# x#) = I# (word2Int# (ctz24# (int2Word# x#)))+#endif++{-# RULES+"fromIntegral/Word8->Int24"   fromIntegral = \(W8# x#) -> I24# (word2Int# x#)+"fromIntegral/Word16->Int24"  fromIntegral = \(W16# x#) -> I24# (word2Int# x#)+"fromIntegral/Int8->Int24"    fromIntegral = \(I8# x#) -> I24# x#+"fromIntegral/Int16->Int24"   fromIntegral = \(I16# x#) -> I24# x#+"fromIntegral/Int24->Int24"   fromIntegral = id :: Int24 -> Int24+"fromIntegral/a->Int24"       fromIntegral = \x -> case fromIntegral x of I# x# -> I24# (narrow24Int# x#)+"fromIntegral/Int24->a"       fromIntegral = \(I24# x#) -> fromIntegral (I# x#)+  #-}++{-# RULES+"properFraction/Float->(Int24,Float)"+    properFraction = \x ->+                      case properFraction x of {+                        (n, y) -> ((fromIntegral :: Int -> Int24) n, y :: Float) }+"truncate/Float->Int24"+    truncate = (fromIntegral :: Int -> Int24) . (truncate :: Float -> Int)+"floor/Float->Int24"+    floor    = (fromIntegral :: Int -> Int24) . (floor :: Float -> Int)+"ceiling/Float->Int24"+    ceiling  = (fromIntegral :: Int -> Int24) . (ceiling :: Float -> Int)+"round/Float->Int24"+    round    = (fromIntegral :: Int -> Int24) . (round  :: Float -> Int)+  #-}++{-# RULES+"properFraction/Double->(Int24,Double)"+    properFraction = \x ->+                      case properFraction x of {+                        (n, y) -> ((fromIntegral :: Int -> Int24) n, y :: Double) }+"truncate/Double->Int24"+    truncate = (fromIntegral :: Int -> Int24) . (truncate :: Double -> Int)+"floor/Double->Int24"+    floor    = (fromIntegral :: Int -> Int24) . (floor :: Double -> Int)+"ceiling/Double->Int24"+    ceiling  = (fromIntegral :: Int -> Int24) . (ceiling :: Double -> Int)+"round/Double->Int24"+    round    = (fromIntegral :: Int -> Int24) . (round  :: Double -> Int)+  #-}++instance Storable Int24 where+  sizeOf _ = 3+  alignment _ = 3+  peek p = fmap fromIntegral $ peek ((castPtr p) :: Ptr Word24)+  poke p v = poke (castPtr p :: Ptr Word24) (fromIntegral v)
+ src/Data/TLSSetting.hs view
@@ -0,0 +1,183 @@+{-# LANGUAGE CPP #-}++-- | Helpers for setting up a tls connection with @tls@ package,+-- for further customization, please refer to @tls@ package.+--+-- Note, functions in this module will throw error if can't load certificates or CA store.+--+module Data.TLSSetting+    ( -- * Choose a CAStore+      TrustedCAStore(..)+      -- * Make TLS settings+    , makeClientParams+    , makeClientParams'+    , makeServerParams+    , makeServerParams'+      -- * Internal+    , mozillaCAStorePath+    ) where++import qualified Data.ByteString            as B+import qualified Data.PEM                   as X509+import qualified Data.X509                  as X509+import qualified Data.X509.CertificateStore as X509+import qualified Network.TLS                as TLS+import qualified Network.TLS.Extra          as TLS+import           Paths_mysql_haskell          (getDataFileName)+import qualified System.X509                as X509++#if !MIN_VERSION_tls(2,1,4)+import           Data.Default.Class         (def)+#endif++-- | The whole point of TLS is that: a peer should have already trusted+-- some certificates, which can be used for validating other peer's certificates.+-- if the certificates sent by other side form a chain. and one of them is issued+-- by one of 'TrustedCAStore', Then the peer will be trusted.+--+data TrustedCAStore+    = SystemCAStore                   -- ^ provided by your operating system.+    | MozillaCAStore                  -- ^ provided by <https://curl.haxx.se/docs/caextract.html Mozilla>.+    | CustomCAStore FilePath          -- ^ provided by your self, the CA file can contain multiple certificates.+  deriving (Show, Eq)++-- | Get the built-in mozilla CA's path.+mozillaCAStorePath :: IO FilePath+mozillaCAStorePath = getDataFileName "mozillaCAStore.pem"++makeCAStore :: TrustedCAStore -> IO X509.CertificateStore+makeCAStore SystemCAStore       = X509.getSystemCertificateStore+makeCAStore MozillaCAStore      = makeCAStore . CustomCAStore =<< mozillaCAStorePath+makeCAStore (CustomCAStore fp)  = do+    bs <- B.readFile fp+    let pems = case X509.pemParseBS bs of+          Right pms -> pms+          Left err -> error err+    case mapM (X509.decodeSignedCertificate . X509.pemContent) pems of+        Right cas -> return (X509.makeCertificateStore cas)+        Left err  -> error err++-- | make a simple tls 'TLS.ClientParams' that will validate server and use tls connection+-- without providing client's own certificate. suitable for connecting server which don't+-- validate clients.+--+-- we defer setting of 'TLS.clientServerIdentification' to connecting phase.+--+-- Note, tls's default validating method require server has v3 certificate.+-- you can use openssl's V3 extension to issue such a certificate. or change 'TLS.ClientParams'+-- before connecting.+--+makeClientParams :: TrustedCAStore          -- ^ trusted certificates.+                 -> IO TLS.ClientParams+makeClientParams tca = do+    caStore <- makeCAStore tca+    return (TLS.defaultParamsClient "" B.empty)+        {   TLS.clientSupported = defaultSupported' { TLS.supportedCiphers = TLS.ciphersuite_default }+        ,   TLS.clientShared    = defaultShared'+            {   TLS.sharedCAStore         = caStore+            ,   TLS.sharedValidationCache = defaultValidationCache'+            }+        }++-- | make a simple tls 'TLS.ClientParams' that will validate server and use tls connection+-- while providing client's own certificate as well. suitable for connecting server which+-- validate clients, such as a MySQL account created with @REQUIRE X509@.+--+-- The certificate is sent to every server that asks for one.+--+-- Also only accept v3 certificate.+--+makeClientParams' :: FilePath       -- ^ public certificate (X.509 format).+                  -> [FilePath]     -- ^ chain certificates (X.509 format).+                                    --   the root of your certificate chain should be+                                    --   already trusted by server, or tls will fail.+                  -> FilePath       -- ^ private key associated.+                  -> TrustedCAStore -- ^ trusted certificates.+                  -> IO TLS.ClientParams+makeClientParams' pub certs priv tca = do+    p <- makeClientParams tca+    c <- TLS.credentialLoadX509Chain pub certs priv+    case c of+        Right credential ->+            return p+                {   TLS.clientHooks = (TLS.clientHooks p)+                    {   TLS.onCertificateRequest = answerCertificateRequest credential+                    }+                }+        Left err -> error err++-- | Answers every certificate request with the client's credential.+--+-- Decision: send it whatever certificate types and issuers the server lists,+-- rather than send none on a mismatch, so a credential the server cannot+-- accept fails the handshake instead of failing the login as a vaguer access+-- denied. A client must answer through this hook: tls reads+-- 'TLS.sharedCredentials' on servers only.+answerCertificateRequest :: TLS.Credential -> request -> IO (Maybe TLS.Credential)+answerCertificateRequest credential _request = pure (Just credential)++-- | make a simple tls 'TLS.ServerParams' without validating client's certificate.+--+makeServerParams :: FilePath        -- ^ public certificate (X.509 format).+                 -> [FilePath]      -- ^ chain certificates (X.509 format).+                                    --   the root of your certificate chain should be+                                    --   already trusted by client, or tls will fail.+                 -> FilePath        -- ^ private key associated.+                 -> IO TLS.ServerParams+makeServerParams pub certs priv = do+    c <- TLS.credentialLoadX509Chain pub certs priv+    case c of+        Right c'@(X509.CertificateChain c'', _) ->+            return defaultServerParams'+                {   TLS.serverCACertificates =  c''+                ,   TLS.serverShared = defaultShared'+                    {+                        TLS.sharedCredentials = TLS.Credentials [c']+                    }+                ,   TLS.serverSupported = defaultSupported' { TLS.supportedCiphers = TLS.ciphersuite_strong }+                }+        Left err -> error err++-- | make a tls 'TLS.ServerParams' that also validating client's certificate.+--+makeServerParams' :: FilePath       -- ^ public certificate (X.509 format).+                  -> [FilePath]     -- ^ chain certificates (X.509 format).+                  -> FilePath       -- ^ private key associated.+                  -> TrustedCAStore -- ^ server will use these certificates to validate clients.+                  -> IO TLS.ServerParams+makeServerParams' pub certs priv tca = do+    caStore <- makeCAStore tca+    p <- makeServerParams pub certs priv+    return p+        {   TLS.serverWantClientCert = True+        ,   TLS.serverShared = (TLS.serverShared p)+            {   TLS.sharedCAStore = caStore+            }+        }++-- Compatibility shims for tls < 2.1.4 vs >= 2.1.4+#if MIN_VERSION_tls(2,1,4)+defaultServerParams' :: TLS.ServerParams+defaultServerParams' = TLS.defaultParamsServer++defaultShared' :: TLS.Shared+defaultShared' = TLS.defaultShared++defaultSupported' :: TLS.Supported+defaultSupported' = TLS.defaultSupported++defaultValidationCache' :: TLS.ValidationCache+defaultValidationCache' = TLS.defaultValidationCache+#else+defaultServerParams' :: TLS.ServerParams+defaultServerParams' = def++defaultShared' :: TLS.Shared+defaultShared' = def++defaultSupported' :: TLS.Supported+defaultSupported' = def++defaultValidationCache' :: TLS.ValidationCache+defaultValidationCache' = def+#endif
+ src/Data/Word/Word24.hs view
@@ -0,0 +1,285 @@+{-# OPTIONS_GHC -Wno-inline-rule-shadowing #-}++{-# LANGUAGE BangPatterns      #-}+{-# LANGUAGE CPP               #-}+{-# LANGUAGE MagicHash         #-}+{-# LANGUAGE NoImplicitPrelude #-}++#if !MIN_VERSION_base(4,8,0)+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE StandaloneDeriving #-}+#endif++-- |+-- Module      : Data.Word.Word24+-- License     : see  src/Data/LICENSE+-- Stability   : experimental+-- Portability : non-portable (GHC Extensions)++-- Provide a 24-bit unsigned integral type: 'Word24', analagous to Word8,+-- Word16, etc.+--++module Data.Word.Word24 (+  -- * Word24 type+    Word24(..)+  , byteSwap24+  , byteSwap24#+  -- * Internal helpers+  , narrow24Word#+#if MIN_VERSION_base(4,8,0)+  , clz24#+  , ctz24#+#endif+  , popCnt24#+  )++where++import           Data.Bits+import           Data.Data+import           Data.Maybe+import           Foreign.Storable++import           GHC.Arr+import           GHC.Base+import           GHC.Enum+import           GHC.Num hiding (integerToWord)+import           GHC.Ptr+import           GHC.Read+import           GHC.Real+import           GHC.Show+import           GHC.Word.Compat+import           GHC.Integer (smallInteger, integerToWord)++import           Control.DeepSeq++#if !MIN_VERSION_base(4,8,0)+import           Data.Typeable+#endif++------------------------------------------------------------------------++-- Word24 is represented in the same way as Word.  Operations may assume and+-- must ensure that it holds only values in its logical range.++-- | 24-bit unsigned integer type+--+data Word24 = W24# Word# deriving (Eq, Ord)++#if !MIN_VERSION_base(4,8,0)+deriving instance Typeable Word24+#endif++instance NFData Word24 where rnf !_ = ()++word24Type :: DataType+word24Type = mkIntType "Data.Word.Word24.Word24"++instance Data Word24 where+  toConstr x = mkIntegralConstr word24Type x+  gunfold _ z c = case constrRep c of+                    (IntConstr x) -> z (fromIntegral x)+                    _ -> error $ "Data.Data.gunfold: Constructor " ++ show c+                                 ++ " is not of type Word24."+  dataTypeOf _ = word24Type++-- | narrowings represented as primop 'and#' in GHC.+narrow24Word# :: Word# -> Word#+narrow24Word# = and# 0xFFFFFF##++#if MIN_VERSION_base(4,8,0)+-- | count leading zeros+--+clz24# :: Word# -> Word#+clz24# w# = clz32# (narrow24Word# w#) `minusWord#` 8##++-- | count trailing zeros+--+ctz24# :: Word# -> Word#+ctz24# w# = ctz# (w# `or#` 0x1000000##)+#endif++-- | the number of set bits+--+popCnt24# :: Word# -> Word#+popCnt24# w# = popCnt# (narrow24Word# w#)++instance Show Word24 where+  showsPrec p x = showsPrec p (fromIntegral x :: Int)++instance Num Word24 where+  (W24# x#) + (W24# y#) = W24# (narrow24Word# (x# `plusWord#` y#))+  (W24# x#) - (W24# y#) = W24# (narrow24Word# (x# `minusWord#` y#))+  (W24# x#) * (W24# y#) = W24# (narrow24Word# (x# `timesWord#` y#))+  negate (W24# x#)      = W24# (narrow24Word# (int2Word# (negateInt# (word2Int# x#))))+  abs x                 = x+  signum 0              = 0+  signum _              = 1+  fromInteger i         = W24# (narrow24Word# (integerToWord i))++instance Real Word24 where+  toRational x = toInteger x % 1++instance Enum Word24 where+  succ x+    | x /= maxBound  = x + 1+    | otherwise      = succError "Word24"+  pred x+    | x /= minBound  = x - 1+    | otherwise      = predError "Word24"+  toEnum i@(I# i#)+    | i >= 0 && i <= fromIntegral (maxBound :: Word24)+                     = W24# (int2Word# i#)+    | otherwise      = toEnumError "Word24" i (minBound::Word24, maxBound::Word24)+  fromEnum (W24# x#) = I# (word2Int# x#)+  enumFrom           = boundedEnumFrom+  enumFromThen       = boundedEnumFromThen++instance Integral Word24 where+  quot (W24# x#) y@(W24# y#)+    | y /= 0                 = W24# (x# `quotWord#` y#)+    | otherwise              = divZeroError+  rem (W24# x#) y@(W24# y#)+    | y /= 0                 = W24# (x# `remWord#` y#)+    | otherwise              = divZeroError+  div (W24# x#) y@(W24# y#)+    | y /= 0                 = W24# (x# `quotWord#` y#)+    | otherwise              = divZeroError+  mod (W24# x#) y@(W24# y#)+    | y /= 0                 = W24# (x# `remWord#` y#)+    | otherwise              = divZeroError+  quotRem (W24# x#) y@(W24# y#)+    | y /= 0                 = (W24# (x# `quotWord#` y#), W24# (x# `remWord#` y#))+    | otherwise              = divZeroError+  divMod (W24# x#) y@(W24# y#)+    | y /= 0                 = (W24# (x# `quotWord#` y#), W24# (x# `remWord#` y#))+    | otherwise              = divZeroError+  toInteger (W24# x#)        = smallInteger (word2Int# x#)++instance Bounded Word24 where+  minBound = 0+  maxBound = 0xFFFFFF++instance Ix Word24 where+  range (m,n)         = [m..n]+  unsafeIndex (m,_) i = fromIntegral (i - m)+  inRange (m,n) i     = m <= i && i <= n++instance Read Word24 where+  readsPrec p s = [(fromIntegral (x::Int), r) | (x, r) <- readsPrec p s]++instance Bits Word24 where+    {-# INLINE shift #-}+    {-# INLINE bit #-}+    {-# INLINE testBit #-}++    (W24# x#) .&.   (W24# y#)  = W24# (x# `and#` y#)+    (W24# x#) .|.   (W24# y#)  = W24# (x# `or#`  y#)+    (W24# x#) `xor` (W24# y#)  = W24# (x# `xor#` y#)+    complement (W24# x#)       = W24# (x# `xor#` mb#) where !(W24# mb#) = maxBound+    (W24# x#) `shift` (I# i#)+        | isTrue# (i# >=# 0#)  = W24# (narrow24Word# (x# `shiftL#` i#))+        | otherwise            = W24# (x# `shiftRL#` negateInt# i#)+    (W24# x#) `shiftL` (I# i#)       = W24# (narrow24Word# (x# `shiftL#` i#))+    (W24# x#) `unsafeShiftL` (I# i#) =+        W24# (narrow24Word# (x# `uncheckedShiftL#` i#))+    (W24# x#) `shiftR`       (I# i#) = W24# (x# `shiftRL#` i#)+    (W24# x#) `unsafeShiftR` (I# i#) = W24# (x# `uncheckedShiftRL#` i#)+    (W24# x#) `rotate`       i+        | isTrue# (i'# ==# 0#) = W24# x#+        | otherwise  = W24# (narrow24Word# ((x# `uncheckedShiftL#` i'#) `or#`+                                            (x# `uncheckedShiftRL#` (24# -# i'#))))+      where+        !(I# i'#) = i `mod` 24+    bitSizeMaybe i            = Just (finiteBitSize i)+    bitSize                   = finiteBitSize+    isSigned _                = False+    popCount (W24# x#)        = I# (word2Int# (popCnt24# x#))+    bit                       = bitDefault+    testBit                   = testBitDefault++instance FiniteBits Word24 where+    finiteBitSize _ = 24+#if MIN_VERSION_base(4,8,0)+    countLeadingZeros  (W24# x#) = I# (word2Int# (clz24# x#))+    countTrailingZeros (W24# x#) = I# (word2Int# (ctz24# x#))+#endif++-- | Swap bytes in 'Word24'.+--+byteSwap24 :: Word24 -> Word24+byteSwap24 (W24# w#) = W24# (byteSwap24# w#)++byteSwap24# :: Word# -> Word#+byteSwap24# w# = let byte0 = uncheckedShiftL# (and# w# 0x0000ff##) 16#+                     byte1 = and# w# 0x00ff00##+                     byte2 = uncheckedShiftRL# (and# w# 0xff0000##) 16#+                 in byte0 `or#` byte1 `or#` byte2++{-# RULES+"fromIntegral/Word8->Word24"    fromIntegral = \(W8# x#) -> W24# x#+"fromIntegral/Word16->Word24"   fromIntegral = \(W16# x#) -> W24# x#+"fromIntegral/Word24->Word24"   fromIntegral = id :: Word24 -> Word24+"fromIntegral/Word24->Integer"  fromIntegral = toInteger :: Word24 -> Integer+"fromIntegral/a->Word24"        fromIntegral = \x -> case fromIntegral x of W# x# -> W24# (narrow24Word# x#)+"fromIntegral/Word24->a"        fromIntegral = \(W24# x#) -> fromIntegral (W# x#)+  #-}++{-# RULES+"properFraction/Float->(Word24,Float)"+    properFraction = \x ->+                      case properFraction x of {+                        (n, y) -> ((fromIntegral :: Int -> Word24) n, y :: Float) }+"truncate/Float->Word24"+    truncate = (fromIntegral :: Int -> Word24) . (truncate :: Float -> Int)+"floor/Float->Word24"+    floor    = (fromIntegral :: Int -> Word24) . (floor :: Float -> Int)+"ceiling/Float->Word24"+    ceiling  = (fromIntegral :: Int -> Word24) . (ceiling :: Float -> Int)+"round/Float->Word24"+    round    = (fromIntegral :: Int -> Word24) . (round  :: Float -> Int)+  #-}++{-# RULES+"properFraction/Double->(Word24,Double)"+    properFraction = \x ->+                      case properFraction x of {+                        (n, y) -> ((fromIntegral :: Int -> Word24) n, y :: Double) }+"truncate/Double->Word24"+    truncate = (fromIntegral :: Int -> Word24) . (truncate :: Double -> Int)+"floor/Double->Word24"+    floor    = (fromIntegral :: Int -> Word24) . (floor :: Double -> Int)+"ceiling/Double->Word24"+    ceiling  = (fromIntegral :: Int -> Word24) . (ceiling :: Double -> Int)+"round/Double->Word24"+    round    = (fromIntegral :: Int -> Word24) . (round  :: Double -> Int)+  #-}++readWord24OffPtr :: Ptr Word24 -> IO Word24+readWord24OffPtr p = do+  let p' = castPtr p :: Ptr Word8+  w1 <- peekElemOff p' 0+  w2 <- peekElemOff p' 1+  w3 <- peekElemOff p' 2+  let w1' = (fromIntegral :: (Word8 -> Word24)) w1+      w2' = (fromIntegral :: (Word8 -> Word24)) w2+      w3' = (fromIntegral :: (Word8 -> Word24)) w3+      w = w1' .|. (w2' `shiftL` 8) .|. (w3' `shiftL` 16)+  return w++writeWord24ToPtr :: Ptr Word24 -> Word24 -> IO ()+writeWord24ToPtr p v = do+    let w1 = fromIntegral (v .&. 0x0000FF) :: Word8+        w2 = fromIntegral ((v .&. 0x00FF00) `shiftR` 8) :: Word8+        w3 = fromIntegral ((v .&. 0xFF0000) `shiftR` 16) :: Word8+    pokeByteOff p 0 w1+    pokeByteOff p 1 w2+    pokeByteOff p 2 w3++instance Storable Word24 where+  sizeOf _    = 3+  alignment _ = 3+  peek        = readWord24OffPtr+  poke        = writeWord24ToPtr+
+ src/Database/MariaDB.hs view
@@ -0,0 +1,354 @@+{-|+Module      : Database.MariaDB+Description : MariaDB-only protocol features+License     : BSD+Stability   : experimental+Portability : PORTABLE++Features only MariaDB servers have, such as extended column metadata and bulk+execution. They rely on MariaDB's own protocol extensions, so this module+works with MariaDB servers only: its connect functions refuse a server that+does not speak them, MySQL or MariaDB before 10.2, with 'NotMariaDB'.++The functions here take a 'MariaDBConn', so they cannot be given a connection+to MySQL. A 'MariaDBConn' wraps an ordinary 'MySQLConn': use+'mysqlConnection' for everything in "Database.MySQL.Base".++A MariaDB server that does not offer a requested capability is not an error.+The connection is made without it, 'mariadbCapabilities' says what was agreed,+and a function that needs the missing capability raises+'CapabilityNotNegotiated'.++@since 1.4.0+-}+module Database.MariaDB+    ( -- * Capabilities+      MariaDBCapability(..)+      -- * Connections+    , MariaDBConn+    , mysqlConnection+    , mariadbGreeting+    , mariadbCapabilities+    , connectMariaDB+    , connectMariaDBUnixSocket+    , connectMariaDBTLS+      -- * Extended column metadata+    , ColumnMetadata(..)+    , ExtendedMetadata(..)+    , prepareStmtMetadata+      -- * Bulk execution+    , BulkParam(..)+    , executeBulk+    , BulkExecuteError(..)+      -- * Exceptions+    , NotMariaDB(..)+    , CapabilityNotNegotiated(..)+    ) where++import           Control.Exception              (Exception (displayException), throwIO)+import           Control.Monad                  (forM_, replicateM, unless, void)+import           Data.Binary.Get                (Get, getWord8, isEmpty)+import           Data.Binary.Put                (Put, putLazyByteString, putWord16le, putWord32le,+                                                 putWord8, runPut)+import           Data.Binary.Parser             (parseOnly)+import           Data.Bits                      ((.&.), (.|.))+import           Data.ByteString                (ByteString)+import qualified Data.ByteString.Lazy           as L+import           Data.List                      (nub, transpose)+import           Data.List.NonEmpty             (NonEmpty ((:|)))+import qualified Data.List.NonEmpty             as NonEmpty+import           Data.Word                      (Word32, Word8)+import           Database.MySQL.Base+import qualified Database.MySQL.Connection      as Connection+import qualified Database.MySQL.TLS             as TLS+import qualified Network.TLS                    as Network.TLS++-- | A capability only MariaDB servers offer. Only capabilities whose replies+-- this library can read are listed, since asking for one changes what the+-- server sends.+data MariaDBCapability+    = MariaDBExtendedMetadata+      -- ^ MARIADB_CLIENT_EXTENDED_METADATA: column definitions name types+      -- such as JSON or UUID, see 'prepareStmtMetadata'.+    | MariaDBBulkOperations+      -- ^ MARIADB_CLIENT_STMT_BULK_OPERATIONS: 'executeBulk'.+    deriving (Show, Eq, Ord, Enum, Bounded)++-- | Bits 32 to 63 of the capability flags, see+-- Note [MariaDB extended capabilities] in "Database.MySQL.Protocol.Auth".+capabilityBit :: MariaDBCapability -> Word32+capabilityBit MariaDBExtendedMetadata = 0x08  -- 1 << (35 - 32)+capabilityBit MariaDBBulkOperations   = 0x04  -- 1 << (34 - 32)++capabilityBits :: [MariaDBCapability] -> ExtendedCapabilities+capabilityBits = ExtendedCapabilities . foldr ((.|.) . capabilityBit) 0++negotiatedCapabilities :: ExtendedCapabilities -> [MariaDBCapability]+negotiatedCapabilities (ExtendedCapabilities bits) =+    filter (\capability -> bits .&. capabilityBit capability /= 0) [minBound .. maxBound]++-- | A connection together with the MariaDB capabilities it negotiated.+data MariaDBConn = MariaDBConn+    { mysqlConnection     :: !MySQLConn+      -- ^ The underlying connection, for everything in "Database.MySQL.Base";+      -- closing it closes the 'MariaDBConn'.+    , mariadbGreeting     :: !Greeting+    , mariadbCapabilities :: ![MariaDBCapability]+      -- ^ Those asked for that the server offered.+    }++hasCapability :: MariaDBCapability -> MariaDBConn -> Bool+hasCapability capability conn = capability `elem` mariadbCapabilities conn++-- | 'Database.MySQL.Base.connect' asking for these capabilities. Raises+-- 'NotMariaDB', after closing the connection, when the server does not speak+-- MariaDB's protocol extensions (MySQL, or MariaDB before 10.2).+connectMariaDB :: [MariaDBCapability] -> ConnectInfo -> IO MariaDBConn+connectMariaDB requested ci =+    Connection.connectDetailExtended (capabilityBits requested) ci >>= mariadbConn++-- | 'Database.MySQL.Base.connectUnixSocket' asking for these capabilities, and+-- refusing a MySQL server as 'connectMariaDB' does.+connectMariaDBUnixSocket :: [MariaDBCapability] -> FilePath -> ConnectInfo -> IO MariaDBConn+connectMariaDBUnixSocket requested socketPath ci =+    Connection.connectUnixSocketDetailExtended (capabilityBits requested) socketPath ci >>= mariadbConn++-- | 'Database.MySQL.TLS.connect' asking for these capabilities, and refusing a+-- MySQL server as 'connectMariaDB' does.+connectMariaDBTLS :: [MariaDBCapability] -> ConnectInfo -> (Network.TLS.ClientParams, String) -> IO MariaDBConn+connectMariaDBTLS requested ci tlsParams =+    TLS.connectDetailExtended (capabilityBits requested) ci tlsParams >>= mariadbConn++-- | Refuses a server whose greeting does not announce MariaDB's protocol+-- extensions, after closing the connection. The greeting's capabilities decide+-- this, not its version string, which @mariadbd --version@ can set to anything.+mariadbConn :: (Greeting, ExtendedCapabilities, MySQLConn) -> IO MariaDBConn+mariadbConn (greeting, negotiated, conn) =+    if offersExtendedCapabilities greeting+    then pure (MariaDBConn conn greeting (negotiatedCapabilities negotiated))+    else do+        close conn+        throwIO (NotMariaDB (greetingVersion greeting))++-- | A column definition with the MariaDB extended metadata the server sent+-- for it.+data ColumnMetadata = ColumnMetadata+    { columnDefinition :: !ColumnDef+    , columnExtended   :: ![ExtendedMetadata]+      -- ^ Empty for columns of ordinary types.+    } deriving (Show, Eq)++-- | One entry of a column's extended metadata.+data ExtendedMetadata+    = ExtendedTypeName !ByteString+      -- ^ The SQL type behind a generic wire type, e.g. @uuid@ or @inet6@.+    | ExtendedFormat !ByteString+      -- ^ How a value is formatted, e.g. @json@ for a JSON column sent as a+      -- string.+    | ExtendedUnrecognised !Word8 !ByteString+      -- ^ An entry kind newer than this library, kept as sent.+    deriving (Show, Eq)++-- | Parse a column definition from a connection with+-- 'MariaDBExtendedMetadata', or without it, giving no extended metadata.+getColumnMetadata :: Get ColumnMetadata+getColumnMetadata = do+    (column, block) <- getFieldExtended+    case parseOnly getExtendedEntries block of+        Left err      -> fail ("MariaDB extended column metadata: " ++ err)+        Right entries -> pure (ColumnMetadata column entries)++getExtendedEntries :: Get [ExtendedMetadata]+getExtendedEntries = do+    done <- isEmpty+    if done then pure [] else (:) <$> getExtendedEntry <*> getExtendedEntries++getExtendedEntry :: Get ExtendedMetadata+getExtendedEntry = extendedEntry <$> getWord8 <*> getLenEncBytes++extendedEntry :: Word8 -> ByteString -> ExtendedMetadata+extendedEntry kind = if+    | kind == 0 -> ExtendedTypeName+    | kind == 1 -> ExtendedFormat+    | otherwise -> ExtendedUnrecognised kind++-- | 'Database.MySQL.Base.prepareStmtDetail' with the extended metadata of+-- every parameter and result column. Use the returned statement with the+-- usual functions of "Database.MySQL.Base".+--+-- Raises 'CapabilityNotNegotiated' without 'MariaDBExtendedMetadata', as the+-- server then sends no extended metadata to return.+--+-- Decision: offered for prepared statements only. A text query variant needs+-- the result-set reader that "Database.MySQL.Base" keeps internal, and+-- preparing a query is how a caller learns its column types up front anyway.+prepareStmtMetadata :: MariaDBConn -> Query -> IO (StmtPrepareOK, [ColumnMetadata], [ColumnMetadata])+prepareStmtMetadata conn (Query stmt) = do+    requireCapability MariaDBExtendedMetadata conn+    let mysqlConn = mysqlConnection conn+        is = Connection.mysqlRead mysqlConn+    Connection.guardUnconsumed mysqlConn+    Connection.writeCommand (COM_STMT_PREPARE stmt) (Connection.mysqlWrite mysqlConn)+    p <- Connection.readPacket is+    if isERR p+    then decodeFromPacket p >>= throwIO . ERRException+    else do+        prepared@(StmtPrepareOK _ columnCount paramCount _) <- getFromPacket getStmtPrepareOK p+        params <- replicateM paramCount (Connection.readPacket is >>= getFromPacket getColumnMetadata)+        unless (paramCount == 0) (void (Connection.readPacket is))  -- EOF+        columns <- replicateM columnCount (Connection.readPacket is >>= getFromPacket getColumnMetadata)+        unless (columnCount == 0) (void (Connection.readPacket is))  -- EOF+        pure (prepared, params, columns)++-- | One parameter of one row given to 'executeBulk'.+data BulkParam+    = BulkValue !MySQLValue+      -- ^ 'MySQLNull' sends SQL NULL.+    | BulkDefault+      -- ^ The column's default, for INSERT and UPDATE.+    | BulkIgnore+      -- ^ The column's default for INSERT; UPDATE leaves the column as it is.+    deriving (Show, Eq)++-- | Rows 'executeBulk' refuses before sending anything, so the connection+-- stays usable.+data BulkExecuteError+    = BulkNoParameters+      -- ^ The rows have no parameters; a bulk command sends parameter rows.+    | BulkRowWidth !Int !Int !Int+      -- ^ Row number (counting from 0), the first row's width, this row's width.+    | BulkMixedTypes !Int ![MySQLValue]+      -- ^ Parameter number (counting from 0) and one value of each type found+      -- there. The command declares one type per parameter for every row.+    deriving (Show, Eq)++instance Exception BulkExecuteError where+    displayException err = "mysql-haskell: executeBulk: " ++ case err of+        BulkNoParameters -> "the rows have no parameters"+        BulkRowWidth row expected found ->+            "row " ++ show row ++ " has " ++ show found ++ " parameters, the first row has " ++ show expected+        BulkMixedTypes param examples ->+            "parameter " ++ show param ++ " mixes value types " ++ show examples+            ++ "; give every row the same MySQLValue constructor there"++-- | Execute a prepared statement once per row in a single round trip+-- (COM_STMT_BULK_EXECUTE). The 'OK' sums the affected rows of every+-- execution. A statement that returns rows is refused by the server with an+-- 'ERRException'.+--+-- Every row must have the same number of parameters, and each parameter the+-- same 'MySQLValue' constructor in every row where it is a 'BulkValue' other+-- than 'MySQLNull', or 'BulkExecuteError' is raised. Raises+-- 'CapabilityNotNegotiated' without 'MariaDBBulkOperations'; MySQL has no+-- bulk command.+--+-- The rows travel in one command, which the server's @max_allowed_packet@+-- limits; split larger inputs over several calls.+--+-- > import Data.List.NonEmpty (NonEmpty ((:|)))+-- >+-- > conn <- connectMariaDB [MariaDBBulkOperations] defaultConnectInfo+-- > let mysqlConn = mysqlConnection conn+-- > _ <- execute_ mysqlConn "CREATE TABLE people (name VARCHAR(20), age INT DEFAULT 18)"+-- > insert <- prepareStmt mysqlConn "INSERT INTO people (name, age) VALUES (?, ?)"+-- > ok <- executeBulk conn insert+-- >     (  [BulkValue (MySQLText "Ada"),   BulkValue (MySQLInt32 36)]+-- >     :| [ [BulkValue (MySQLText "Alan"),  BulkValue MySQLNull]  -- age NULL+-- >        , [BulkValue (MySQLText "Grace"), BulkDefault]          -- age 18+-- >        ])+-- > okAffectedRows ok  -- 3+executeBulk :: MariaDBConn -> StmtID -> NonEmpty [BulkParam] -> IO OK+executeBulk conn stid rows = do+    requireCapability MariaDBBulkOperations conn+    types <- either throwIO pure (bulkParamTypes rows)+    let mysqlConn = mysqlConnection conn+        is = Connection.mysqlRead mysqlConn+    Connection.guardUnconsumed mysqlConn+    Connection.writePayload (runPut (putBulkExecute stid types rows)) (Connection.mysqlWrite mysqlConn)+    p <- Connection.readPacket is+    if  | isERR p -> decodeFromPacket p >>= throwIO . ERRException+        | isOK  p -> decodeFromPacket p+        | otherwise -> throwIO (UnexpectedPacket p)++-- | The type each parameter is declared with: that of its values, or NULL's+-- when every row sends NULL, DEFAULT or IGNORE there.+bulkParamTypes :: NonEmpty [BulkParam] -> Either BulkExecuteError [L.ByteString]+bulkParamTypes rows@(firstRow :| _) =+    if null firstRow+    then Left BulkNoParameters+    else do+        mapM_ (checkRowWidth (length firstRow)) (zip [0 ..] (NonEmpty.toList rows))+        mapM paramType (zip [0 ..] (transpose (NonEmpty.toList rows)))++checkRowWidth :: Int -> (Int, [BulkParam]) -> Either BulkExecuteError ()+checkRowWidth expected (row, params) =+    if length params == expected then Right () else Left (BulkRowWidth row expected (length params))++paramType :: (Int, [BulkParam]) -> Either BulkExecuteError L.ByteString+paramType (param, column) =+    let values = concatMap typedValue column+        typed = nubOn (runPut . putParamMySQLType) values+    in case typed of+        []      -> Right (runPut (putParamMySQLType MySQLNull))+        [value] -> Right (runPut (putParamMySQLType value))+        (_ : _ : _) -> Left (BulkMixedTypes param typed)++typedValue :: BulkParam -> [MySQLValue]+typedValue (BulkValue MySQLNull) = []+typedValue (BulkValue value)     = [value]+typedValue BulkDefault           = []+typedValue BulkIgnore            = []++-- | One value per distinct key, in order of first appearance.+nubOn :: Eq key => (value -> key) -> [value] -> [value]+nubOn key values =+    let keys = nub (map key values)+    in concatMap (\k -> take 1 (filter ((== k) . key) values)) keys++-- Decision: always send the parameter types (SEND_TYPES_TO_SERVER, 128), as+-- mariadb-connector-c does. The server keeps no types from an earlier+-- COM_STMT_EXECUTE that a bulk command could rely on. Per-row results+-- (SEND_UNIT_RESULTS) need MARIADB_CLIENT_BULK_UNIT_RESULTS, which MariaDB+-- 11.4, the version CI runs, does not offer.+putBulkExecute :: StmtID -> [L.ByteString] -> NonEmpty [BulkParam] -> Put+putBulkExecute stid types rows = do+    putWord8 0xfa+    putWord32le stid+    putWord16le 128+    mapM_ putLazyByteString types+    forM_ rows (mapM_ putBulkParam)++putBulkParam :: BulkParam -> Put+putBulkParam (BulkValue MySQLNull) = putWord8 1+putBulkParam (BulkValue value)     = putWord8 0 >> putBinaryField value+putBulkParam BulkDefault           = putWord8 2+putBulkParam BulkIgnore            = putWord8 3++requireCapability :: MariaDBCapability -> MariaDBConn -> IO ()+requireCapability capability conn =+    unless (hasCapability capability conn) (throwIO (CapabilityNotNegotiated capability))++-- | A function needed a capability the connection did not negotiate, because+-- it was not asked for in 'connectMariaDB' or the server's MariaDB version+-- does not offer it.+newtype CapabilityNotNegotiated = CapabilityNotNegotiated MariaDBCapability+    deriving Show++instance Exception CapabilityNotNegotiated where+    displayException (CapabilityNotNegotiated capability) =+        "mysql-haskell: this function needs the MariaDB capability " ++ show capability+        ++ ", which the connection did not negotiate. Ask for it in connectMariaDB,"+        ++ " on a MariaDB version that offers it."++-- | A connect function of this module reached a server that does not speak+-- MariaDB's protocol extensions: MySQL, or MariaDB before 10.2. Carries the+-- server's version string.+newtype NotMariaDB = NotMariaDB ByteString+    deriving Show++instance Exception NotMariaDB where+    displayException (NotMariaDB version) =+        "mysql-haskell: Database.MariaDB needs a server speaking MariaDB's protocol"+        ++ " extensions, but server " ++ show version ++ " set CLIENT_MYSQL in its"+        ++ " greeting, as MySQL servers and MariaDB before 10.2 do. Connect with"+        ++ " Database.MySQL.Base for those servers, or upgrade to MariaDB 10.2 or later."
+ src/Database/MySQL/Base.hs view
@@ -0,0 +1,767 @@+{-|+Module      : Database.MySQL.Base+Description : Prelude of mysql-haskell+Copyright   : (c) Winterland, 2016+License     : BSD+Maintainer  : drkoster@qq.com+Stability   : experimental+Portability : PORTABLE++This module provide common MySQL operations,++NOTEs on 'Exception's: This package use 'Exception' to deal with unexpected situations,+but you shouldn't try to catch them if you don't have a recovery plan,+for example: there's no meaning to catch a 'ERRException' during authentication unless you want to try different passwords.+By using this library you will meet:++    * 'NetworkException':  underline network is broken.+    * 'UnconsumedResultSet':  you should consume previous resultset before sending new command.+    * 'AbandonedReply': reading an earlier reply stopped part-way, e.g. on a timeout;+      connect again.+    * 'ERRException':  you receive a 'ERR' packet when you shouldn't.+    * 'UnexpectedPacket':  you receive a unexpected packet when you shouldn't.+    * 'DecodePacketException': there's a packet we can't decode.+    * 'WrongParamsCount': you're giving wrong number of params to 'renderParams'.+    * 'ExtraResultSets': a statement produced a result-set the function running it+      could not return.++Both 'UnexpectedPacket' and 'DecodePacketException' may indicate a bug of this library rather your code, so please report!++-}+module Database.MySQL.Base+    ( -- * Setting up and control connection+      MySQLConn+    , ConnectInfo(..)+    , defaultConnectInfo+    , defaultConnectInfoMB4+    , connect+    , connectDetail+    , connectUnixSocket+    , connectUnixSocketDetail+    , close+    , ping+    , resetConnection+      -- * Direct query+    , execute+    , executeMany+    , executeMany_+    , execute_+    , query_+    , queryVector_+    , query+    , queryVector+    , queryMulti_+    , queryMulti+    , StatementResult(..)+      -- * Prepared query statement+    , prepareStmt+    , prepareStmtDetail+    , executeStmt+    , queryStmt+    , queryStmtVector+    , closeStmt+    , resetStmt+      -- * Decoding rows without 'MySQLValue'+      -- | Rows decoded straight into Haskell values with the decoders of+      -- "Database.MySQL.Decoder", or handed over raw for libraries that decode+      -- by column number.+    , queryRows_+    , queryRows+    , queryStmtRows+    , queryRawRows_+    , queryStmtRawRows+      -- * Helpers+    , withTransaction+    , QueryParam(..)+    , Param (..)+    , Query(..)+    , renderParams+    , command+    , Stream.skipToEof+      -- * Exceptions+    , NetworkException(..)+    , UnconsumedResultSet(..)+    , AbandonedReply(..)+    , ERRException(..)+    , UnexpectedPacket(..)+    , DecodePacketException(..)+    , WrongParamsCount(..)+    , ExtraResultSets(..)+      -- * MySQL protocol+    , module  Database.MySQL.Protocol.Auth+    , module  Database.MySQL.Protocol.Command+    , module  Database.MySQL.Protocol.ColumnDef+    , module  Database.MySQL.Protocol.Packet+    , module  Database.MySQL.Protocol.MySQLValue+    , module  Database.MySQL.Protocol.RawRow+    ) where++import           Control.Exception                  (SomeException, catch, fromException,+                                                     mask, onException, throwIO)+import           Control.Monad+import           Data.Bits                          ((.&.))+import           Data.ByteString                    (ByteString)+import qualified Data.ByteString                    as B+import qualified Data.ByteString.Lazy               as L+import           Data.IORef                         (IORef, newIORef, readIORef, writeIORef)+import           Data.Maybe                         (isJust)+import           Data.Proxy                         (Proxy (..))+import           Database.MySQL.Connection+import qualified Database.MySQL.Decoder             as Decoder+import           Database.MySQL.Protocol.Auth+import           Database.MySQL.Protocol.ColumnDef+import           Database.MySQL.Protocol.Command+import           Database.MySQL.Protocol.MySQLValue+import           Database.MySQL.Protocol.Packet+import           Database.MySQL.Protocol.RawRow++import           Database.MySQL.Query+import           System.IO.Streams                  (InputStream)+import qualified System.IO.Streams                  as Stream+import qualified Data.Vector                        as V+import qualified Unwitch.Convert.Int                as Int++--------------------------------------------------------------------------------++-- | Execute a MySQL query with parameters which don't return a result-set.+--+-- The query may contain placeholders @?@, for filling up parameters, the parameters+-- will be escaped before get filled into the query. Under @NO_BACKSLASH_ESCAPES@+-- a backslash in a parameter is stored twice, and on a gbk, big5, sjis, cp932 or+-- gb18030 connection the escaping is not safe at all, see+-- "Database.MySQL.Protocol.Escape". You should consider using prepared statement+-- if this's not an one shot query, and must on those character sets.+--+execute :: QueryParam p => MySQLConn -> Query -> [p] -> IO OK+execute conn qry params = execute_ conn (renderParams qry params)++{-# SPECIALIZE execute :: MySQLConn -> Query -> [MySQLValue] -> IO OK #-}+{-# SPECIALIZE execute :: MySQLConn -> Query -> [Param]      -> IO OK #-}++-- | Execute a multi-row query which don't return result-set.+--+-- Leverage MySQL's multi-statement support to do batch insert\/update\/delete,+-- you may want to use 'withTransaction' to make sure it's atomic, and+-- use @sum . map okAffectedRows@ to get all affected rows count.+--+-- The result holds one 'OK' per statement, so a query with several statements+-- gives several per parameter set. A statement that returns rows raises+-- 'ExtraResultSets' once the whole reply has been read; 'queryMulti' returns+-- those. No parameter sets gives @[]@ without contacting the server.+--+-- @since 0.2.0.0+--+executeMany :: QueryParam p => MySQLConn -> Query -> [[p]] -> IO [OK]+executeMany conn@(MySQLConn is os _ _) qry paramsList =+    case paramsList of+        -- Joining no statements would send an empty query, which the server refuses.+        [] -> guardUnconsumed conn >> pure []+        _ : _ -> runCommand conn $ do+            let qry' = L.intercalate ";" $ map (fromQuery . renderParams qry) paramsList+            writeCommand (COM_QUERY qry') os+            readStatementResults is >>= onlyOKs++{-# SPECIALIZE executeMany :: MySQLConn -> Query -> [[MySQLValue]] -> IO [OK] #-}+{-# SPECIALIZE executeMany :: MySQLConn -> Query -> [[Param]]      -> IO [OK] #-}++-- | Execute multiple querys (without param) which don't return result-set.+--+-- This's useful when your want to execute multiple SQLs without params, e.g. from a+-- SQL dump, or a table migration plan.+--+-- A statement that returns rows raises 'ExtraResultSets' once the whole reply+-- has been read; 'queryMulti_' returns those.+--+-- @since 0.8.4.0+--+executeMany_ :: MySQLConn -> Query -> IO [OK]+executeMany_ conn@(MySQLConn is os _ _) qry = runCommand conn $ do+    writeCommand (COM_QUERY (fromQuery qry)) os+    readStatementResults is >>= onlyOKs++-- | Execute a MySQL query which don't return a result-set.+--+-- For a multi-statement query this is the first statement's 'OK'; the later+-- ones are read and discarded ('executeMany_' returns them all).+--+execute_ :: MySQLConn -> Query -> IO OK+execute_ conn (Query qry) = executeCommand conn (COM_QUERY qry)++-- | Execute a MySQL query which return a result-set with parameters.+--+-- Note that you must fully consumed the result-set before start a new query on+-- the same 'MySQLConn', or an 'UnconsumedResultSet' will be thrown.+-- if you want to skip the result-set, use 'Stream.skipToEof'.+--+query :: QueryParam p => MySQLConn -> Query -> [p] -> IO ([ColumnDef], InputStream [MySQLValue])+query conn qry params = query_ conn (renderParams qry params)++{-# SPECIALIZE query :: MySQLConn -> Query -> [MySQLValue] -> IO ([ColumnDef], InputStream [MySQLValue]) #-}+{-# SPECIALIZE query :: MySQLConn -> Query -> [Param]      -> IO ([ColumnDef], InputStream [MySQLValue]) #-}++-- | 'V.Vector' version of 'query'.+--+-- @since 0.5.1.0+--+queryVector :: QueryParam p => MySQLConn -> Query -> [p] -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))+queryVector conn qry params = queryVector_ conn (renderParams qry params)++{-# SPECIALIZE queryVector :: MySQLConn -> Query -> [MySQLValue] -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue)) #-}+{-# SPECIALIZE queryVector :: MySQLConn -> Query -> [Param]      -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue)) #-}++-- | Execute a MySQL query which return a result-set.+--+-- A statement without a result-set, such as an INSERT, gives no columns and no+-- rows; use 'execute_' to get its 'OK'.+--+query_ :: MySQLConn -> Query -> IO ([ColumnDef], InputStream [MySQLValue])+query_ conn@(MySQLConn is os _ state) (Query qry) = runCommand conn $ do+    writeCommand (COM_QUERY qry) os+    reply <- readQueryReply is+    case reply of+        WithoutResultSet -> (,) [] <$> Stream.nullInput+        ResultSetColumns len -> do+            fields <- replicateM len $ (decodeFromPacket <=< readPacket) is+            _ <- readPacket is -- eof packet, we don't verify this though+            writeIORef state RowsUnread+            rows <- resultSetRows is state (decodeTextRowPacket (map columnKind fields))+            return (fields, rows)++-- | 'V.Vector' version of 'query_'.+--+-- @since 0.5.1.0+--+queryVector_ :: MySQLConn -> Query -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))+queryVector_ conn@(MySQLConn is os _ state) (Query qry) = runCommand conn $ do+    writeCommand (COM_QUERY qry) os+    reply <- readQueryReply is+    case reply of+        WithoutResultSet -> (,) V.empty <$> Stream.nullInput+        ResultSetColumns len -> do+            fields <- V.replicateM len $ (decodeFromPacket <=< readPacket) is+            _ <- readPacket is -- eof packet, we don't verify this though+            writeIORef state RowsUnread+            rows <- resultSetRows is state (decodeTextRowVectorPacket (V.map columnKind fields))+            return (fields, rows)++-- | 'query_' with each row decoded by a 'Decoder.RowDecoder' instead of into+-- 'MySQLValue's. The decoder is checked against the column definitions before+-- any row is read: a result set it does not fit raises 'Decoder.ColumnMismatch'.+-- A field that does not decode raises 'Decoder.FieldError' when its row is read.+-- Either way the remaining rows are skipped first, so the connection stays+-- usable.+--+-- A statement without a result-set, such as an INSERT, gives no columns and no+-- rows, as with 'query_'.+--+-- > import qualified Database.MySQL.Decoder as Decode+-- > import qualified System.IO.Streams as Streams+-- >+-- > data Employee = Employee { number :: Int32, born :: Day, name :: Text }+-- >+-- > employee :: Decode.RowDecoder Employee+-- > employee = Employee+-- >     <$> Decode.field Decode.int32+-- >     <*> Decode.field Decode.day+-- >     <*> Decode.field Decode.text+-- >+-- > (_, rows) <- queryRows_ employee conn "SELECT emp_no, birth_date, first_name FROM employees"+-- > employees <- Streams.toList rows+--+-- @since 1.4.0+queryRows_ :: Decoder.RowDecoder a -> MySQLConn -> Query -> IO ([ColumnDef], InputStream a)+queryRows_ decoder conn qry =+    queryRowBodies_ conn qry >>= decodeRowBodies (Proxy :: Proxy TextProtocol) decoder++-- | 'queryRows_' with parameters, filled in as 'query' does. With the+-- @employee@ decoder from 'queryRows_':+--+-- > (_, rows) <- queryRows employee conn+-- >     "SELECT emp_no, birth_date, first_name FROM employees WHERE first_name = ?"+-- >     [MySQLText "Georgi"]+-- > georgis <- Streams.toList rows+--+-- @since 1.4.0+queryRows :: QueryParam p => Decoder.RowDecoder a -> MySQLConn -> Query -> [p] -> IO ([ColumnDef], InputStream a)+queryRows decoder conn qry params = queryRows_ decoder conn (renderParams qry params)++{-# SPECIALIZE queryRows :: Decoder.RowDecoder a -> MySQLConn -> Query -> [MySQLValue] -> IO ([ColumnDef], InputStream a) #-}+{-# SPECIALIZE queryRows :: Decoder.RowDecoder a -> MySQLConn -> Query -> [Param]      -> IO ([ColumnDef], InputStream a) #-}++-- | 'queryStmt' with each row decoded by a 'Decoder.RowDecoder', as+-- 'queryRows_' does. With the @employee@ decoder from 'queryRows_':+--+-- > statement <- prepareStmt conn+-- >     "SELECT emp_no, birth_date, first_name FROM employees WHERE emp_no > ?"+-- > (_, rows) <- queryStmtRows employee conn statement [MySQLInt32 10001]+-- > later <- Streams.toList rows+--+-- @since 1.4.0+queryStmtRows :: Decoder.RowDecoder a -> MySQLConn -> StmtID -> [MySQLValue] -> IO ([ColumnDef], InputStream a)+queryStmtRows decoder conn stid params =+    queryStmtRowBodies conn stid params >>= decodeRowBodies (Proxy :: Proxy BinaryProtocol) decoder++decodeRowBodies :: forall protocol a. Decoder.RowProtocol protocol+                => Proxy protocol -> Decoder.RowDecoder a -> ([ColumnDef], InputStream ByteString)+                -> IO ([ColumnDef], InputStream a)+decodeRowBodies _ decoder (fields, bodies) = case fields of+    [] -> (,) [] <$> Stream.nullInput+    _  -> case Decoder.prepareRowDecoder @protocol decoder (V.fromList fields) of+        Left mismatch  -> Stream.skipToEof bodies >> throwIO mismatch+        Right prepared -> (,) fields <$> Stream.mapM (decodeRowBody prepared bodies) bodies++-- | A row that does not decode skips the rest first, so the connection stays+-- usable.+decodeRowBody :: Decoder.PreparedRow protocol a -> InputStream ByteString -> ByteString -> IO a+decodeRowBody prepared bodies row = case Decoder.runPreparedRow prepared row of+    Left fieldError -> Stream.skipToEof bodies >> throwIO fieldError+    Right value     -> pure value++-- | 'query_' handing each row over as a 'RawRow', with the bounds of its+-- fields, for libraries that decode by column number with+-- 'Decoder.prepareFieldParser' and 'Decoder.runFieldParser'. A parser is+-- prepared once per column from its definition, then run on every row; it+-- passes the field to a continuation instead of returning an 'Either':+--+-- > import qualified Database.MySQL.Decoder as Decode+-- > import qualified System.IO.Streams as Streams+-- >+-- > (columns, rows) <- queryRawRows_ conn "SELECT emp_no, first_name FROM employees"+-- > numberColumn : nameColumn : _ <- pure columns+-- > numbers <- either throwIO pure (Decode.prepareFieldParser Decode.int32 (ColumnNumber 0) numberColumn)+-- > names <- either throwIO pure (Decode.prepareFieldParser Decode.text (ColumnNumber 1) nameColumn)+-- > rawRows <- Streams.toList rows+-- > forM_ rawRows $ \row ->+-- >     Decode.runFieldParser numbers row (ColumnNumber 0) throwIO $ \number ->+-- >     Decode.runFieldParser names row (ColumnNumber 1) throwIO $ \name ->+-- >     print (number, name)+--+-- @since 1.4.0+queryRawRows_ :: MySQLConn -> Query -> IO ([ColumnDef], InputStream (RawRow TextProtocol))+queryRawRows_ conn qry = do+    (fields, bodies) <- queryRowBodies_ conn qry+    (,) fields <$> Stream.mapM (rawRowOrThrow (textRawRow (length fields))) bodies++-- | 'queryStmt' handing each row over as a 'RawRow', as 'queryRawRows_' does.+-- The parsers are prepared and run the same way; they read the binary+-- protocol's fields because the rows are 'RawRow' 'BinaryProtocol':+--+-- > statement <- prepareStmt conn "SELECT emp_no, first_name FROM employees WHERE emp_no > ?"+-- > (columns, rows) <- queryStmtRawRows conn statement [MySQLInt32 10001]+-- > numberColumn : nameColumn : _ <- pure columns+-- > numbers <- either throwIO pure (Decode.prepareFieldParser Decode.int32 (ColumnNumber 0) numberColumn)+-- > names <- either throwIO pure (Decode.prepareFieldParser Decode.text (ColumnNumber 1) nameColumn)+-- > rawRows <- Streams.toList rows+-- > forM_ rawRows $ \row ->+-- >     Decode.runFieldParser numbers row (ColumnNumber 0) throwIO $ \number ->+-- >     Decode.runFieldParser names row (ColumnNumber 1) throwIO $ \name ->+-- >     print (number, name)+--+-- @since 1.4.0+queryStmtRawRows :: MySQLConn -> StmtID -> [MySQLValue] -> IO ([ColumnDef], InputStream (RawRow BinaryProtocol))+queryStmtRawRows conn stid params = do+    (fields, bodies) <- queryStmtRowBodies conn stid params+    (,) fields <$> Stream.mapM (rawRowOrThrow (binaryRawRow (V.fromList (map binaryWidth fields)))) bodies++rawRowOrThrow :: (ByteString -> Either RowError (RawRow protocol)) -> ByteString -> IO (RawRow protocol)+rawRowOrThrow toRawRow row = either (throwRowError row) pure (toRawRow row)++-- | The bodies of a text-protocol result set's row packets.+queryRowBodies_ :: MySQLConn -> Query -> IO ([ColumnDef], InputStream ByteString)+queryRowBodies_ conn@(MySQLConn is os _ state) (Query qry) = runCommand conn $ do+    writeCommand (COM_QUERY qry) os+    reply <- readQueryReply is+    case reply of+        WithoutResultSet -> (,) [] <$> Stream.nullInput+        ResultSetColumns len -> do+            fields <- replicateM len $ (decodeFromPacket <=< readPacket) is+            _ <- readPacket is -- eof packet, we don't verify this though+            writeIORef state RowsUnread+            rows <- resultSetRows is state (pure . L.toStrict . pBody)+            return (fields, rows)++-- | The bodies of a prepared statement's row packets.+queryStmtRowBodies :: MySQLConn -> StmtID -> [MySQLValue] -> IO ([ColumnDef], InputStream ByteString)+queryStmtRowBodies conn@(MySQLConn is os _ state) stid params = runCommand conn $ do+    writeCommand (COM_STMT_EXECUTE stid params (makeNullMap params)) os+    reply <- readQueryReply is+    case reply of+        WithoutResultSet -> (,) [] <$> Stream.nullInput+        ResultSetColumns len -> do+            fields <- replicateM len $ (decodeFromPacket <=< readPacket) is+            _ <- readPacket is -- eof packet, we don't verify this though+            writeIORef state RowsUnread+            rows <- resultSetRows is state binaryRowBody+            return (fields, rows)++-- | A binary-protocol row, whose packets start with 0x00 like an OK packet.+binaryRowBody :: Packet -> IO ByteString+binaryRowBody packet =+    if isOK packet then pure (L.toStrict (pBody packet)) else throwIO (UnexpectedPacket packet)++-- | One result of a statement run through 'queryMulti_'.+--+-- @since 1.3.3+data StatementResult+    = StatementOK OK                            -- ^ a statement without rows, e.g. an INSERT+    | StatementRows [ColumnDef] [[MySQLValue]]  -- ^ a result-set+    deriving (Show, Eq)++-- | Execute a MySQL query and return every result it produces, in order: one per+-- statement of a multi-statement query, and for a CALL one per result-set the+-- procedure returns followed by the CALL's own 'OK'.+--+-- Unlike 'query_' the rows are read up front, as each result-set has to be read+-- off the connection before the next one arrives. A failing statement raises its+-- 'ERRException'; the server runs no statement after it.+--+-- @since 1.3.3+queryMulti_ :: MySQLConn -> Query -> IO [StatementResult]+queryMulti_ conn@(MySQLConn is os _ _) (Query qry) = runCommand conn $ do+    writeCommand (COM_QUERY qry) os+    readStatementResults is++-- | 'queryMulti_' with parameters, filled in as 'query' does.+--+-- @since 1.3.3+queryMulti :: QueryParam p => MySQLConn -> Query -> [p] -> IO [StatementResult]+queryMulti conn qry params = queryMulti_ conn (renderParams qry params)++{-# SPECIALIZE queryMulti :: MySQLConn -> Query -> [MySQLValue] -> IO [StatementResult] #-}+{-# SPECIALIZE queryMulti :: MySQLConn -> Query -> [Param]      -> IO [StatementResult] #-}++-- | Read a reply's results until one comes without SERVER_MORE_RESULTS_EXISTS.+readStatementResults :: InputStream Packet -> IO [StatementResult]+readStatementResults is = do+    p <- readPacket is+    if  | isERR p -> decodeFromPacket p >>= throwIO . ERRException+        | isOK  p -> do+            ok <- decodeFromPacket p+            if isThereMore ok+            then (StatementOK ok :) <$> readStatementResults is+            else pure [StatementOK ok]+        | otherwise -> do+            (result, eof) <- readResultSet is p+            if isThereMoreAfterEOF eof+            then (result :) <$> readStatementResults is+            else pure [result]++-- | Read the text-protocol result-set this column-count packet starts, with the+-- EOF packet that closes it.+readResultSet :: InputStream Packet -> Packet -> IO (StatementResult, EOF)+readResultSet is columnCountPacket = do+    columnCount <- getFromPacket getLenEncInt columnCountPacket+    fields <- replicateM columnCount ((decodeFromPacket <=< readPacket) is)+    _ <- readPacket is -- eof packet after the column definitions+    (rows, eof) <- readTextRows (map columnKind fields) [] is+    pure (StatementRows fields rows, eof)++-- | Read text-protocol rows up to the EOF packet that ends them; the rows read+-- so far are kept in reverse.+readTextRows :: [ColumnKind] -> [[MySQLValue]] -> InputStream Packet -> IO ([[MySQLValue]], EOF)+readTextRows columns earlierRows is = do+    q <- readPacket is+    if  | isEOF q -> (,) (reverse earlierRows) <$> decodeFromPacket q+        | isERR q -> decodeFromPacket q >>= throwIO . ERRException+        | otherwise -> do+            row <- decodeTextRowPacket columns q+            readTextRows columns (row : earlierRows) is++-- | The 'OK's of a reply that should hold nothing else; a result-set among them+-- raises 'ExtraResultSets', after the whole reply has been read.+onlyOKs :: [StatementResult] -> IO [OK]+onlyOKs results = case results of+    [] -> pure []+    StatementOK ok : rest -> (ok :) <$> onlyOKs rest+    StatementRows _ _ : _ -> throwIO ExtraResultSets++-- | How the server answered a statement sent through a query function.+data QueryReply+    = WithoutResultSet      -- ^ only OK packets, e.g. for an INSERT+    | ResultSetColumns Int  -- ^ a result-set with this many columns follows++-- | Read the reply to a query function's statement up to its first result-set.+--+-- An OK packet must be told apart here: its leading 0x00 also decodes as a+-- column count of zero, which used to leave the caller waiting forever for an+-- EOF packet the server never sends (issue #47). OKs flagged with more results+-- are skipped, so @SET \@x := 1; SELECT \@x@ gives the SELECT's rows.+--+-- Decision: answer such a statement with an empty result-set rather than an+-- exception, so code that worked around the hang keeps working and the fix is+-- not a breaking change. The OK's affected-rows count is dropped; 'execute_'+-- returns it.+readQueryReply :: InputStream Packet -> IO QueryReply+readQueryReply is = do+    p <- readPacket is+    if  | isERR p -> decodeFromPacket p >>= throwIO . ERRException+        | isOK  p -> do+            ok <- decodeFromPacket p+            if isThereMore ok then readQueryReply is else pure WithoutResultSet+        | otherwise -> ResultSetColumns <$> getFromPacket getLenEncInt p++-- | A text-protocol row packet decoded with 'decodeTextRow', raising+-- 'DecodePacketFailed' as 'getFromPacket' does.+decodeTextRowPacket :: [ColumnKind] -> Packet -> IO [MySQLValue]+decodeTextRowPacket columns packet = do+    let row = L.toStrict (pBody packet)+    either (throwRowError row) pure (decodeTextRow columns row)++-- | 'V.Vector' version of 'decodeTextRowPacket'.+decodeTextRowVectorPacket :: V.Vector ColumnKind -> Packet -> IO (V.Vector MySQLValue)+decodeTextRowVectorPacket columns packet = do+    let row = L.toStrict (pBody packet)+    either (throwRowError row) pure (decodeTextRowVector columns row)++throwRowError :: ByteString -> RowError -> IO a+throwRowError row rowError =+    throwIO (DecodePacketFailed (B.drop offset row) (Int.toInt64 offset)+                                (describeRowError rowError))+  where+    offset = rowErrorOffset rowError++-- | The rows of a result-set, read as they are asked for, up to its EOF packet.+--+-- Once finished, also when 'finishAfterEOF' throws, further reads answer+-- Nothing rather than wait on the socket for packets that will never come.+-- A read stopped part-way leaves the connection at 'ReplyAbandoned', see+-- Note [Abandoned replies].+resultSetRows :: InputStream Packet -> IORef ReplyState -> (Packet -> IO row) -> IO (InputStream row)+resultSetRows is state decodeRow = do+    finished <- newIORef False+    Stream.makeInputStream $ mask $ \restore -> do+        alreadyFinished <- readIORef finished+        current <- readIORef state+        if  | alreadyFinished -> pure Nothing+            | current == ReplyAbandoned -> throwIO AbandonedReply+            | otherwise -> restore (nextRow is state finished decodeRow)+                `catch` settleFailedRowRead state finished++nextRow :: InputStream Packet -> IORef ReplyState -> IORef Bool -> (Packet -> IO row) -> IO (Maybe row)+nextRow is state finished decodeRow = do+    q <- readPacket is+    if  | isEOF q -> do+            writeIORef finished True+            finishAfterEOF is q+            writeIORef state RepliesRead+            pure Nothing+        | isERR q -> decodeFromPacket q >>= throwIO . ERRException+        | otherwise -> Just <$> decodeRow q++-- | Like 'settleFailedExchange', and a row that failed to decode was still+-- read in full, so the rest of the rows can be read or skipped.+settleFailedRowRead :: IORef ReplyState -> IORef Bool -> SomeException -> IO a+settleFailedRowRead state finished failure = do+    if  | endsReply failure -> writeIORef finished True >> writeIORef state RepliesRead+        | isJust (fromException failure :: Maybe DecodePacketException) -> pure ()+        | otherwise -> writeIORef state ReplyAbandoned+    throwIO failure++-- | A binary-protocol row, whose packets start with 0x00 like an OK packet. A+-- row that does not decode raises 'DecodePacketFailed', as it did when rows+-- were decoded with 'getBinaryRow'.+binaryValuesPacket :: Decoder.BinaryValueColumns -> Packet -> IO [MySQLValue]+binaryValuesPacket columns packet = do+    row <- binaryRowBody packet+    case Decoder.decodeBinaryValues columns row of+        Left fieldError -> throwIO (DecodePacketFailed row 0 (show fieldError))+        Right values    -> pure values++-- | What followed a reply flagged SERVER_MORE_RESULTS_EXISTS.+data FurtherResults+    = OnlyOKs        -- ^ e.g. the closing OK of a CALL, or more INSERTs' OKs+    | SomeResultSet  -- ^ at least one result-set, which was discarded++-- | Finish a reply that ended in this 'OK', see 'skipFurtherResults'.+finishAfterOK :: InputStream Packet -> OK -> IO ()+finishAfterOK is ok =+    when (isThereMore ok) (skipFurtherResults OnlyOKs is >>= throwOnResultSet)++-- | Finish a result-set at its EOF packet, see 'skipFurtherResults'.+finishAfterEOF :: InputStream Packet -> Packet -> IO ()+finishAfterEOF is eofPacket = do+    eof <- decodeFromPacket eofPacket+    when (isThereMoreAfterEOF eof) (skipFurtherResults OnlyOKs is >>= throwOnResultSet)++-- | SERVER_MORE_RESULTS_EXISTS on a result-set's closing EOF packet.+isThereMoreAfterEOF :: EOF -> Bool+isThereMoreAfterEOF eof = eofStatus eof .&. 0x08 /= 0++-- | Read every result that follows a reply flagged SERVER_MORE_RESULTS_EXISTS.+--+-- The client asks for multi-statements and multi-results, so a CALL or a query+-- holding several statements gets one result per statement. Any left unread+-- would be taken by the next command as its own reply, shifting every later+-- query's results by one.+skipFurtherResults :: FurtherResults -> InputStream Packet -> IO FurtherResults+skipFurtherResults seen is = do+    p <- readPacket is+    if  | isERR p -> decodeFromPacket p >>= throwIO . ERRException+        | isOK  p -> do+            ok <- decodeFromPacket p+            if isThereMore ok then skipFurtherResults seen is else pure seen+        | otherwise -> do+            eof <- skipResultSet is p+            if isThereMoreAfterEOF eof+            then skipFurtherResults SomeResultSet is+            else pure SomeResultSet++-- | Read the result-set this column-count packet starts, returning its closing+-- EOF packet.+skipResultSet :: InputStream Packet -> Packet -> IO EOF+skipResultSet is columnCountPacket = do+    columnCount <- getFromPacket getLenEncInt columnCountPacket+    replicateM_ columnCount (readPacket is)+    _ <- readPacket is -- eof packet after the column definitions+    skipRows is++-- | Read a result-set's rows up to and including the EOF packet that ends them.+skipRows :: InputStream Packet -> IO EOF+skipRows is = do+    q <- readPacket is+    if  | isEOF q -> decodeFromPacket q+        | isERR q -> decodeFromPacket q >>= throwIO . ERRException+        | otherwise -> skipRows is++throwOnResultSet :: FurtherResults -> IO ()+throwOnResultSet further = case further of+    OnlyOKs -> pure ()+    SomeResultSet -> throwIO ExtraResultSets++-- | Ask MySQL to prepare a query statement.+--+prepareStmt :: MySQLConn -> Query -> IO StmtID+prepareStmt conn@(MySQLConn is os _ _) (Query stmt) = runCommand conn $ do+    writeCommand (COM_STMT_PREPARE stmt) os+    p <- readPacket is+    if isERR p+    then decodeFromPacket p >>= throwIO . ERRException+    else do+        StmtPrepareOK stid colCnt paramCnt _ <- getFromPacket getStmtPrepareOK p+        _ <- replicateM_ paramCnt (readPacket is)+        _ <- unless (paramCnt == 0) (void (readPacket is))  -- EOF+        _ <- replicateM_ colCnt (readPacket is)+        _ <- unless (colCnt == 0) (void (readPacket is))  -- EOF+        return stid++-- | Ask MySQL to prepare a query statement.+--+-- All details from @COM_STMT_PREPARE@ Response are returned: the 'StmtPrepareOK' packet,+-- params's 'ColumnDef', result's 'ColumnDef'.+--+prepareStmtDetail :: MySQLConn -> Query -> IO (StmtPrepareOK, [ColumnDef], [ColumnDef])+prepareStmtDetail conn@(MySQLConn is os _ _) (Query stmt) = runCommand conn $ do+    writeCommand (COM_STMT_PREPARE stmt) os+    p <- readPacket is+    if isERR p+    then decodeFromPacket p >>= throwIO . ERRException+    else do+        sOK@(StmtPrepareOK _ colCnt paramCnt _) <- getFromPacket getStmtPrepareOK p+        pdefs <- replicateM paramCnt ((decodeFromPacket <=< readPacket) is)+        _ <- unless (paramCnt == 0) (void (readPacket is))  -- EOF+        cdefs <- replicateM colCnt ((decodeFromPacket <=< readPacket) is)+        _ <- unless (colCnt == 0) (void (readPacket is))  -- EOF+        return (sOK, pdefs, cdefs)++-- | Ask MySQL to closed a query statement.+--+closeStmt :: MySQLConn -> StmtID -> IO ()+closeStmt (MySQLConn _ os _ _) stid = do+    writeCommand (COM_STMT_CLOSE stid) os++-- | Ask MySQL to reset a query statement: discard the data sent for its+-- parameters and close its cursor.+--+-- It does not discard a result-set the statement returned: the server has+-- already sent its rows, so they must be read or skipped first, or this throws+-- 'UnconsumedResultSet'.+--+resetStmt :: MySQLConn -> StmtID -> IO ()+resetStmt conn@(MySQLConn is os _ _) stid = runCommand conn $ do+    writeCommand (COM_STMT_RESET stid) os+    p <- readPacket is+    when (isERR p) (decodeFromPacket p >>= throwIO . ERRException)++-- | Execute prepared query statement with parameters, expecting no resultset.+--+executeStmt :: MySQLConn -> StmtID -> [MySQLValue] -> IO OK+executeStmt conn stid params =+  executeCommand conn (COM_STMT_EXECUTE stid params (makeNullMap params))++-- | Send a statement that should answer with an 'OK' and read its whole reply,+-- see 'skipFurtherResults'. A result-set, also as the first reply (a SELECT or+-- a CALL running one), is read off the connection and raises 'ExtraResultSets',+-- unless a later statement fails: its 'ERRException' is raised instead.+executeCommand :: MySQLConn -> Command -> IO OK+executeCommand conn@(MySQLConn is os _ _) cmd = runCommand conn $ do+    writeCommand cmd os+    p <- readPacket is+    if  | isERR p -> decodeFromPacket p >>= throwIO . ERRException+        | isOK  p -> do+            ok <- decodeFromPacket p+            finishAfterOK is ok+            pure ok+        | otherwise -> do+            eof <- skipResultSet is p+            when (isThereMoreAfterEOF eof) (void (skipFurtherResults SomeResultSet is))+            throwIO ExtraResultSets++-- | Execute prepared query statement with parameters, expecting resultset.+--+-- Rules about 'UnconsumedResultSet' applied here too. A statement without a+-- result-set gives no columns and no rows; use 'executeStmt' to get its 'OK'.+--+queryStmt :: MySQLConn -> StmtID -> [MySQLValue] -> IO ([ColumnDef], InputStream [MySQLValue])+queryStmt conn@(MySQLConn is os _ state) stid params = runCommand conn $ do+    writeCommand (COM_STMT_EXECUTE stid params (makeNullMap params)) os+    reply <- readQueryReply is+    case reply of+        WithoutResultSet -> (,) [] <$> Stream.nullInput+        ResultSetColumns len -> do+            fields <- replicateM len $ (decodeFromPacket <=< readPacket) is+            _ <- readPacket is -- eof packet, we don't verify this though+            writeIORef state RowsUnread+            rows <- resultSetRows is state (binaryValuesPacket (Decoder.binaryValueColumns fields))+            return (fields, rows)++-- | 'V.Vector' version of 'queryStmt'+--+-- @since 0.5.1.0+--+queryStmtVector :: MySQLConn -> StmtID -> [MySQLValue] -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))+queryStmtVector conn@(MySQLConn is os _ state) stid params = runCommand conn $ do+    writeCommand (COM_STMT_EXECUTE stid params (makeNullMap params)) os+    reply <- readQueryReply is+    case reply of+        WithoutResultSet -> (,) V.empty <$> Stream.nullInput+        ResultSetColumns len -> do+            fields <- V.replicateM len $ (decodeFromPacket <=< readPacket) is+            _ <- readPacket is -- eof packet, we don't verify this though+            writeIORef state RowsUnread+            rows <- resultSetRows is state+                (fmap V.fromList . binaryValuesPacket (Decoder.binaryValueColumns (V.toList fields)))+            return (fields, rows)++-- | Run querys inside a transaction, querys will be rolled back if exception arise.+--+-- The procedure's exception is the one rethrown. When the connection cannot+-- take the ROLLBACK, because the procedure left a result-set unread or was+-- interrupted mid-reply, it is left at 'AbandonedReply' instead, and the server+-- rolls the transaction back when the connection closes.+--+-- @since 0.2.0.0+--+withTransaction :: MySQLConn -> IO a -> IO a+withTransaction conn procedure = mask $ \restore -> do+  _ <- execute_ conn "BEGIN"+  r <- restore procedure `onException` rollbackOrAbandon conn+  _ <- execute_ conn "COMMIT"+  pure r++rollbackOrAbandon :: MySQLConn -> IO ()+rollbackOrAbandon conn@(MySQLConn _ _ _ state) = do+  current <- readIORef state+  case current of+    RepliesRead -> void (execute_ conn "ROLLBACK")+    RowsUnread -> writeIORef state ReplyAbandoned+    ReplyAbandoned -> pure ()
+ src/Database/MySQL/BinLog.hs view
@@ -0,0 +1,452 @@+{-|+Module      : Database.MySQL.BinLog+Description : Binary protocol toolkit+Copyright   : (c) Winterland, 2016+License     : BSD+Maintainer  : drkoster@qq.com+Stability   : experimental+Portability : PORTABLE++This module provide tools for binlog listening and row based binlog decoding.+-}++module Database.MySQL.BinLog+    ( -- * binlog utilities+      SlaveID+    , BinLogTracker(..)+    , registerPesudoSlave+    , dumpBinLog+    , RowBinLogEvent(..)+    , decodeRowBinLogEvent+    , RowEventDecodeException(..)+    , decodeBinLogPacket+    , BinLogChecksumMismatch(..)+    , BinLogChecksum(..)+    -- * helpers+    , getLastBinLogTracker+    , isCheckSumEnabled+    , isSemiSyncEnabled+    -- * re-export+    , module Database.MySQL.BinLogProtocol.BinLogEvent+    , module Database.MySQL.BinLogProtocol.BinLogValue+    , module Database.MySQL.BinLogProtocol.BinLogMeta+    ) where++import           Control.Exception                         (Exception (..),+                                                            throwIO)+import           Control.Monad+import           Data.Binary.Put+import           Data.ByteString                           (ByteString)+import qualified Data.ByteString.Char8                     as B8+import qualified Data.ByteString.Lazy                      as L+import           Data.IORef                                (IORef, newIORef,+                                                            readIORef,+                                                            writeIORef)+import           Data.Bits                                 (shiftL, (.&.), (.|.))+import           Data.Text.Encoding                        (encodeUtf8)+import           Data.Word+import           Database.MySQL.Base+import           Database.MySQL.BinLogProtocol.BinLogEvent+import           Database.MySQL.BinLogProtocol.BinLogMeta+import           Database.MySQL.BinLogProtocol.BinLogValue+import           Database.MySQL.BinLogProtocol.CRC32       (crc32)+import           Database.MySQL.Connection+import           GHC.Generics                              (Generic)+import           Numeric                                   (showHex)+import           System.IO.Streams                         (InputStream)+import qualified System.IO.Streams                         as Stream+import qualified Unwitch.Convert.Word8                     as Word8++type SlaveID = Word32++-- | binlog filename and position to start listening.+--+data BinLogTracker = BinLogTracker+    { btFileName :: {-# UNPACK #-} !ByteString+    , btNextPos  :: {-# UNPACK #-} !Word32+    } deriving (Show, Eq, Generic)++-- | Register a pesudo slave to master, although MySQL document suggests you should call this+-- before calling 'dumpBinLog', but it seems it's not really necessary.+--+registerPesudoSlave :: MySQLConn -> SlaveID -> IO OK+registerPesudoSlave conn sid = command conn (COM_REGISTER_SLAVE sid "" "" "" 0 0 0)++-- | Setup binlog listening on given connection, during listening+-- the connection CAN NOT be used to do query, or an 'UnconsumedResultSet' will be thrown.+--+dumpBinLog :: MySQLConn               -- ^ connection to be listened+           -> SlaveID                 -- ^ a number for our pesudo slave.+           -> BinLogTracker           -- ^ binlog position+           -> Bool                    -- ^ if master support semi-ack, do we want to enable it?+                                      -- if master doesn't support, this parameter will be ignored.+           -> IO (FormatDescription, IORef ByteString, InputStream BinLogPacket)+                -- ^ 'FormatDescription', 'IORef' contains current binlog filename, 'BinLogPacket' stream.+dumpBinLog conn@(MySQLConn is wp _ state) sid (BinLogTracker initfn initpos) wantAck = do+    guardUnconsumed conn+    checksum <- isCheckSumEnabled conn+    when checksum $ void $ execute_ conn "SET @master_binlog_checksum = @@global.binlog_checksum"+    semiAck  <- isSemiSyncEnabled conn+    let needAck = semiAck && wantAck+    when needAck . void $ execute_ conn "SET @rpl_semi_sync_slave = 1"+    writeCommand (COM_BINLOG_DUMP initpos 0x00 sid initfn) wp+    writeIORef state RowsUnread++    fref <- newIORef initfn+    rp <- skipToPacketT (readBinLogPacket checksum needAck fref is) BINLOG_ROTATE_EVENT+    re <- getFromBinLogPacket getRotateEvent rp+    writeIORef fref (rFileName re)++    p <- skipToPacketT (readBinLogPacket checksum needAck fref is) BINLOG_FORMAT_DESCRIPTION_EVENT+    replyAck needAck p fref wp+    fmt <- getFromBinLogPacket getFormatDescription p++    es <- Stream.makeInputStream $ do+        q <- readBinLogPacket checksum needAck fref is+        case q of+            Nothing   -> writeIORef state RepliesRead >> return Nothing+            Just q' -> do when (blEventType q' == BINLOG_ROTATE_EVENT) $ do+                                e <- getFromBinLogPacket getRotateEvent q'+                                writeIORef' fref (rFileName e)+                          replyAck needAck q' fref wp+                          return q+    return (fmt, fref, es)+  where+    skipToPacketT iop typ = do+        p <- iop+        case p of+            Just p' -> do+                if blEventType p' == typ then return p' else skipToPacketT iop typ+            Nothing -> throwIO NetworkException++    replyAck needAck p fref wp' = when (needAck && blSemiAck p) $ do+        fn <- readIORef fref+        wp' (makeSemiAckPacket (blLogPos p) fn)++    makeSemiAckPacket pos fn = putToPacket 0 $ do+        putWord8 0xEF      -- semi-ack+        putWord64le pos+        putByteString fn++    readBinLogPacket checksum needAck fref' is' = do+        p <- readPacket is'+        if  | isOK p -> do+                fileName <- readIORef fref'+                Just <$> decodeBinLogPacket checksum needAck fileName p+            | isEOF p -> return Nothing+            | isERR p -> decodeFromPacket p >>= throwIO . ERRException+            | otherwise -> error ("unkown package" <> show p)++-- | Decode a packet of a binlog dump as 'getBinLogPacket' does with the same+-- two flags, and check the CRC32 checksum of the event in it when the event+-- carries one. 'dumpBinLog' reads every packet this way.+--+-- Throws 'BinLogChecksumMismatch' when the checksum does not match the+-- event's bytes, rather than decode a corrupted event, and+-- 'DecodePacketException' when the packet holds no binlog event. See+-- Note [Binlog event checksums] for which events carry a checksum.+--+-- @since 1.5.0+decodeBinLogPacket :: Bool        -- ^ whether the dump's events carry checksums, as 'isCheckSumEnabled' says+                   -> Bool        -- ^ whether the packet has a semi-sync header+                   -> ByteString  -- ^ the binlog file the dump is reading, for the exception+                   -> Packet+                   -> IO BinLogPacket+decodeBinLogPacket checksum semiAck fileName packet = do+    event <- getFromPacket (getBinLogPacket checksum semiAck) packet+    let eventType = blEventType event+        eventBytes = L.drop (if semiAck then 3 else 1) (pBody packet)+        expected = carriedChecksum eventBytes+        actual = computedChecksum eventType eventBytes+    when (carriesChecksum checksum eventType eventBytes && expected /= actual) $+        throwIO (BinLogChecksumMismatch eventType fileName (blLogPos event) expected actual)+    pure event++-- Decision: an event other than a format description event carries a checksum+-- when the dump's setting says so, the setting 'getBinLogPacket' strips four+-- bytes by. A replica follows the algorithm the last format description event+-- names instead, which differs for a binlog file written while binlog_checksum+-- was NONE; such a file now fails its checks, where it used to lose the last+-- four bytes of each event without a word.++-- | See Note [Binlog event checksums].+carriesChecksum :: Bool -> BinLogEventType -> L.ByteString -> Bool+carriesChecksum checksum eventType eventBytes =+    if eventType == BINLOG_FORMAT_DESCRIPTION_EVENT+        then L.indexMaybe eventBytes (L.length eventBytes - 5) == Just checksumAlgorithmCRC32+        else checksum++-- | @BINLOG_CHECKSUM_ALG_CRC32@, as a format description event names it.+checksumAlgorithmCRC32 :: Word8+checksumAlgorithmCRC32 = 1++-- | The checksum in an event's last four bytes.+carriedChecksum :: L.ByteString -> BinLogChecksum+carriedChecksum eventBytes =+    BinLogChecksum (L.foldr prependByte 0 (L.drop (L.length eventBytes - 4) eventBytes))++prependByte :: Word8 -> Word32 -> Word32+prependByte byte rest = Word8.toWord32 byte .|. (rest `shiftL` 8)++-- | The CRC32 of an event's bytes before its checksum. See+-- Note [Binlog event checksums].+computedChecksum :: BinLogEventType -> L.ByteString -> BinLogChecksum+computedChecksum eventType eventBytes =+    let checked = L.take (L.length eventBytes - 4) eventBytes+    in BinLogChecksum+        (crc32 (if eventType == BINLOG_FORMAT_DESCRIPTION_EVENT then clearInUseFlag checked else checked))++-- | Clear @LOG_EVENT_BINLOG_IN_USE_F@ (0x01) in the low byte of an event's+-- flags, which is its 18th byte.+clearInUseFlag :: L.ByteString -> L.ByteString+clearInUseFlag bytes =+    L.take 17 bytes <> L.map (.&. 0xFE) (L.take 1 (L.drop 17 bytes)) <> L.drop 18 bytes++-- | A binlog event whose CRC32 checksum does not match its bytes, as when it+-- was corrupted between the server's binlog and this client.+--+-- @since 1.5.0+data BinLogChecksumMismatch = BinLogChecksumMismatch+    { mismatchEventType :: !BinLogEventType+    , mismatchFileName  :: !ByteString+      -- ^ The binlog file the dump was reading when the event arrived.+    , mismatchLogPos    :: !Word64+      -- ^ The position after the event that its header gives, as 'blLogPos';+      -- 0 for an event the server makes up for the dump, such as its first+      -- rotate event.+    , mismatchExpected  :: !BinLogChecksum+      -- ^ The checksum the event carries.+    , mismatchActual    :: !BinLogChecksum+      -- ^ The CRC32 of the event's bytes.+    } deriving (Show, Eq)++-- | The CRC32 checksum of a binlog event.+--+-- @since 1.5.0+newtype BinLogChecksum = BinLogChecksum Word32+    deriving (Show, Eq)++instance Exception BinLogChecksumMismatch where+    displayException mismatch =+        "mysql-haskell: the binlog event of type "+        ++ show (binLogEventTypeCode (mismatchEventType mismatch))+        ++ " (" ++ show (mismatchEventType mismatch) ++ ") ending at position "+        ++ show (mismatchLogPos mismatch) ++ " of " ++ B8.unpack (mismatchFileName mismatch)+        ++ " fails its CRC32 checksum: it carries " ++ showChecksum (mismatchExpected mismatch)+        ++ ", its bytes give " ++ showChecksum (mismatchActual mismatch)+        ++ ". The event was corrupted on its way from the server's binlog, so it is not"+        ++ " decoded. If the binlog file was written while binlog_checksum was NONE, its"+        ++ " events have no checksum, and a dump from a server whose binlog_checksum is"+        ++ " now CRC32 cannot read that file."++showChecksum :: BinLogChecksum -> String+showChecksum (BinLogChecksum checksum) =+    let digits = showHex checksum ""+    in "0x" ++ replicate (8 - length digits) '0' ++ digits++{- Note [Binlog event checksums]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+With binlog_checksum = CRC32, MySQL (5.6.1 and up) and MariaDB end each event+with the CRC32 of the event's bytes before it, header and body alike. A+replica checks it over the event as the packet delivers it, not over the+length the event's header gives (MySQL's Log_event_footer::event_checksum_test+on read_event's packet length, MariaDB's event_checksum_test in+sql/rpl_utility.cc), and so does 'decodeBinLogPacket'.++The format description event differs twice. Its checksum is computed with+LOG_EVENT_BINLOG_IN_USE_F cleared, since the server clears that flag in place+when it closes the binlog file, so a check clears it as well. And the byte+before its checksum names the algorithm of the events after it, yet the event+always ends in four checksum bytes; when that byte says off (0), the server+does not recompute them after rewriting the event's position for a dump. A+replica therefore checks a format description event only when its own byte+says CRC32 (1), whatever the dump's setting.+-}++-- | Row based binlog event type.+--+-- It's recommended to enable row query event before 'dumpBinLog', so that you can get+-- 'RowQueryEvent' in row based binlog(it's important for detect a table change for example),+-- more information please refer <http://dev.mysql.com/doc/refman/5.7/en/replication-options-binary-log.html#sysvar_binlog_rows_query_log_events sysvar_binlog_rows_query_log_events>+--+-- A 'BinLogTracker' is included so that you can roll up your own HA solutions,+-- for example, writing the tracker to zookeeper when you done with an event.+--+-- The first 'Word32' field is a timestamp present when this event is logged.+--+data RowBinLogEvent+    = RowQueryEvent  {-# UNPACK #-} !Word32 !BinLogTracker !QueryEvent'+    | RowDeleteEvent {-# UNPACK #-} !Word32 !BinLogTracker !TableMapEvent !DeleteRowsEvent+    | RowWriteEvent  {-# UNPACK #-} !Word32 !BinLogTracker !TableMapEvent !WriteRowsEvent+    | RowUpdateEvent {-# UNPACK #-} !Word32 !BinLogTracker !TableMapEvent !UpdateRowsEvent+  deriving (Show, Eq, Generic)++-- | decode row based event from 'BinLogPacket' stream.+--+-- Throws 'RowEventDecodeException' on an event it cannot decode that may+-- carry row changes, rather than skip it and lose them.+decodeRowBinLogEvent :: (FormatDescription, IORef ByteString, InputStream BinLogPacket)+                     -> IO (InputStream RowBinLogEvent)+decodeRowBinLogEvent (fd, fref, is) = do+    tableMaps <- newIORef []+    Stream.makeInputStream (nextRowEvent fd fref tableMaps is)++-- | The table maps of the statement being read, by table ID. A statement maps+-- every table it changes before its rows events, which then name the table by+-- ID; a long statement splits its rows over several events.+type TableMaps = IORef [(Word64, TableMapEvent)]++nextRowEvent :: FormatDescription -> IORef ByteString -> TableMaps -> InputStream BinLogPacket+             -> IO (Maybe RowBinLogEvent)+nextRowEvent fd fref tableMaps is = do+    p <- Stream.read is+    case p of+        Nothing -> return Nothing+        Just packet -> case blEventType packet of+            BINLOG_ROWS_QUERY_EVENT -> do+                tr <- track packet fref+                e <- getFromBinLogPacket getQueryEvent' packet+                pure (Just (RowQueryEvent (blTimestamp packet) tr e))+            BINLOG_TABLE_MAP_EVENT -> do+                tme <- getFromBinLogPacket (getTableMapEvent fd) packet+                maps <- readIORef tableMaps+                writeIORef tableMaps ((tmTableId tme, tme) : maps)+                nextRowEvent fd fref tableMaps is+            BINLOG_WRITE_ROWS_EVENTv1 -> writeRows fd fref tableMaps packet+            BINLOG_WRITE_ROWS_EVENTv2 -> writeRows fd fref tableMaps packet+            BINLOG_DELETE_ROWS_EVENTv1 -> deleteRows fd fref tableMaps packet+            BINLOG_DELETE_ROWS_EVENTv2 -> deleteRows fd fref tableMaps packet+            BINLOG_UPDATE_ROWS_EVENTv1 -> updateRows fd fref tableMaps packet+            BINLOG_UPDATE_ROWS_EVENTv2 -> updateRows fd fref tableMaps packet+            BINLOG_UNRECOGNIZED_EVENT _ -> if isIgnorableEvent packet+                then nextRowEvent fd fref tableMaps is+                else unsupported packet fref+            -- The rows events of MySQL 5.1.0 to 5.1.15, which this decoder never read.+            BINLOG_WRITE_ROWS_EVENTv0 -> unsupported packet fref+            BINLOG_UPDATE_ROWS_EVENTv0 -> unsupported packet fref+            BINLOG_DELETE_ROWS_EVENTv0 -> unsupported packet fref+            -- Events without row changes.+            BINLOG_UNKNOWN_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_START_EVENT_V3 -> nextRowEvent fd fref tableMaps is+            BINLOG_QUERY_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_STOP_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_ROTATE_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_INTVAR_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_LOAD_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_SLAVE_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_CREATE_FILE_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_APPEND_BLOCK_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_EXEC_LOAD_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_DELETE_FILE_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_NEW_LOAD_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_RAND_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_USER_VAR_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_FORMAT_DESCRIPTION_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_XID_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_BEGIN_LOAD_QUERY_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_EXECUTE_LOAD_QUERY_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_INCIDENT_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_HEARTBEAT_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_IGNORABLE_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_GTID_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_ANONYMOUS_GTID_EVENT -> nextRowEvent fd fref tableMaps is+            BINLOG_PREVIOUS_GTIDS_EVENT -> nextRowEvent fd fref tableMaps is++unsupported :: BinLogPacket -> IORef ByteString -> IO a+unsupported packet fref = do+    tr <- track packet fref+    throwIO (UnsupportedEventType (binLogEventTypeCode (blEventType packet)) tr)++writeRows, deleteRows, updateRows+    :: FormatDescription -> IORef ByteString -> TableMaps -> BinLogPacket -> IO (Maybe RowBinLogEvent)+writeRows fd fref tableMaps packet = do+    (tr, tme) <- rowsEventContext fd fref tableMaps packet+    e <- getFromBinLogPacket' (getWriteRowEvent fd tme) packet+    endStatement tableMaps (writeFlags e)+    pure (Just (RowWriteEvent (blTimestamp packet) tr tme e))+deleteRows fd fref tableMaps packet = do+    (tr, tme) <- rowsEventContext fd fref tableMaps packet+    e <- getFromBinLogPacket' (getDeleteRowEvent fd tme) packet+    endStatement tableMaps (deleteFlags e)+    pure (Just (RowDeleteEvent (blTimestamp packet) tr tme e))+updateRows fd fref tableMaps packet = do+    (tr, tme) <- rowsEventContext fd fref tableMaps packet+    e <- getFromBinLogPacket' (getUpdateRowEvent fd tme) packet+    endStatement tableMaps (updateFlags e)+    pure (Just (RowUpdateEvent (blTimestamp packet) tr tme e))++-- | The position of a rows event and the table map its table ID refers to.+rowsEventContext :: FormatDescription -> IORef ByteString -> TableMaps -> BinLogPacket+                 -> IO (BinLogTracker, TableMapEvent)+rowsEventContext fd fref tableMaps packet = do+    tr <- track packet fref+    tableId <- getFromBinLogPacket (getRowsEventTableId fd (blEventType packet)) packet+    maps <- readIORef tableMaps+    case lookup tableId maps of+        Just tme -> pure (tr, tme)+        Nothing -> throwIO (MissingTableMap tableId tr)++-- | @STMT_END_F@ marks a statement's last rows event; its table maps are not+-- used after it, and table IDs may be reused for other tables later.+endStatement :: TableMaps -> Word16 -> IO ()+endStatement tableMaps rowsFlags =+    when (rowsFlags .&. 0x0001 /= 0) (writeIORef tableMaps [])++track :: BinLogPacket -> IORef ByteString -> IO BinLogTracker+track p fref = BinLogTracker <$> readIORef fref <*> (pure . fromIntegral . blLogPos) p++-- | Why 'decodeRowBinLogEvent' stopped.+data RowEventDecodeException+    = UnsupportedEventType !Word8 !BinLogTracker+      -- ^ An event type mysql-haskell cannot decode that the server did not+      -- mark ignorable, such as MySQL's compressed @TRANSACTION_PAYLOAD_EVENT@+      -- (40), so it may carry row changes.+    | MissingTableMap !Word64 !BinLogTracker+      -- ^ A rows event for a table ID whose table map was not read, as when+      -- the dump started in the middle of a statement.+    deriving (Show, Eq)++instance Exception RowEventDecodeException where+    displayException failure = case failure of+        UnsupportedEventType code tracker ->+            "mysql-haskell: binlog event type " ++ show code ++ " at " ++ show tracker+            ++ " cannot be decoded and is not marked ignorable, so it may hold row"+            ++ " changes. For MySQL's TRANSACTION_PAYLOAD_EVENT (40), turn off"+            ++ " binlog_transaction_compression."+        MissingTableMap tableId tracker ->+            "mysql-haskell: the rows event at " ++ show tracker ++ " is for table ID "+            ++ show tableId ++ ", whose table map event was not read. Start the dump"+            ++ " at a transaction boundary, such as getLastBinLogTracker's position."++-- | Get latest master's binlog filename and position.+--+getLastBinLogTracker :: MySQLConn -> IO (Maybe BinLogTracker)+getLastBinLogTracker conn = do+    (_, is) <- query_ conn "SHOW MASTER STATUS"+    row <- Stream.read is+    Stream.skipToEof is+    case row of+        Just (MySQLText fn : MySQLInt64U pos : _) -> return . Just $ BinLogTracker (encodeUtf8 fn) (fromIntegral pos)+        _                                         -> return Nothing++-- | Return True if binlog_checksum = CRC32. Only for MySQL > 5.6+--+isCheckSumEnabled :: MySQLConn -> IO Bool+isCheckSumEnabled conn = do+    (_, is) <- query_ conn "SHOW GLOBAL VARIABLES LIKE 'binlog_checksum'"+    row <- Stream.read is+    Stream.skipToEof is+    case row of+        Just [_, MySQLText "CRC32"] -> return True+        _                           -> return False++-- | Return True if rpl_semi_sync_master_enabled = ON. Only for MySQL > 5.5+--+isSemiSyncEnabled :: MySQLConn -> IO Bool+isSemiSyncEnabled conn = do+    (_, is) <- query_ conn "SHOW VARIABLES LIKE 'rpl_semi_sync_master_enabled'"+    row <- Stream.read is+    Stream.skipToEof is+    case row of+        Just [_, MySQLText "ON"] -> return True+        _                        -> return False
+ src/Database/MySQL/BinLogProtocol/BinLogEvent.hs view
@@ -0,0 +1,394 @@+{-# OPTIONS_GHC -funbox-strict-fields #-}++{-|+Module      : Database.MySQL.BinLogProtocol.BinLogEvent+Description : Binlog event+Copyright   : (c) Winterland, 2016+License     : BSD+Maintainer  : drkoster@qq.com+Stability   : experimental+Portability : PORTABLE++Binlog event type+-}++module Database.MySQL.BinLogProtocol.BinLogEvent where++import           Control.Monad+import           Control.Monad.Loops                       (untilM)+import           Data.Binary+import           Data.Binary.Parser+import           Data.Bits+import           Data.ByteString                           (ByteString)+import qualified Data.ByteString                           as B+import qualified Data.ByteString.Lazy                      as L+import qualified Data.ByteString.Unsafe                    as B+import           Database.MySQL.BinLogProtocol.BinLogMeta+import           Database.MySQL.BinLogProtocol.BinLogValue+import           Database.MySQL.Protocol.Packet+import           Database.MySQL.Protocol.MySQLValue+import           Database.MySQL.Protocol.ColumnDef++import           Control.Exception                         (throwIO)+import           Database.MySQL.Query+import           GHC.Generics                              (Generic)+import qualified Unwitch.Convert.Word32                    as Word32+import qualified Unwitch.Convert.Word8                     as Word8++--------------------------------------------------------------------------------+-- | binlog tyoe+--+data BinLogEventType+    = BINLOG_UNKNOWN_EVENT+    | BINLOG_START_EVENT_V3+    | BINLOG_QUERY_EVENT+    | BINLOG_STOP_EVENT+    | BINLOG_ROTATE_EVENT+    | BINLOG_INTVAR_EVENT+    | BINLOG_LOAD_EVENT+    | BINLOG_SLAVE_EVENT+    | BINLOG_CREATE_FILE_EVENT+    | BINLOG_APPEND_BLOCK_EVENT+    | BINLOG_EXEC_LOAD_EVENT+    | BINLOG_DELETE_FILE_EVENT+    | BINLOG_NEW_LOAD_EVENT+    | BINLOG_RAND_EVENT+    | BINLOG_USER_VAR_EVENT+    | BINLOG_FORMAT_DESCRIPTION_EVENT+    | BINLOG_XID_EVENT+    | BINLOG_BEGIN_LOAD_QUERY_EVENT+    | BINLOG_EXECUTE_LOAD_QUERY_EVENT+    | BINLOG_TABLE_MAP_EVENT+    | BINLOG_WRITE_ROWS_EVENTv0+    | BINLOG_UPDATE_ROWS_EVENTv0+    | BINLOG_DELETE_ROWS_EVENTv0+    | BINLOG_WRITE_ROWS_EVENTv1+    | BINLOG_UPDATE_ROWS_EVENTv1+    | BINLOG_DELETE_ROWS_EVENTv1+    | BINLOG_INCIDENT_EVENT+    | BINLOG_HEARTBEAT_EVENT+    | BINLOG_IGNORABLE_EVENT+    | BINLOG_ROWS_QUERY_EVENT+    | BINLOG_WRITE_ROWS_EVENTv2+    | BINLOG_UPDATE_ROWS_EVENTv2+    | BINLOG_DELETE_ROWS_EVENTv2+    | BINLOG_GTID_EVENT+    | BINLOG_ANONYMOUS_GTID_EVENT+    | BINLOG_PREVIOUS_GTIDS_EVENT+    | BINLOG_UNRECOGNIZED_EVENT !Word8+      -- ^ A type code this library has no constructor for, such as MySQL 8's+      -- @TRANSACTION_PAYLOAD_EVENT@ (40) or MariaDB's 160 and up.+  deriving (Show, Eq)++-- | The type of the event with this code in the binlog header.+binLogEventTypeFromCode :: Word8 -> BinLogEventType+binLogEventTypeFromCode code = case code of+    0 -> BINLOG_UNKNOWN_EVENT+    1 -> BINLOG_START_EVENT_V3+    2 -> BINLOG_QUERY_EVENT+    3 -> BINLOG_STOP_EVENT+    4 -> BINLOG_ROTATE_EVENT+    5 -> BINLOG_INTVAR_EVENT+    6 -> BINLOG_LOAD_EVENT+    7 -> BINLOG_SLAVE_EVENT+    8 -> BINLOG_CREATE_FILE_EVENT+    9 -> BINLOG_APPEND_BLOCK_EVENT+    10 -> BINLOG_EXEC_LOAD_EVENT+    11 -> BINLOG_DELETE_FILE_EVENT+    12 -> BINLOG_NEW_LOAD_EVENT+    13 -> BINLOG_RAND_EVENT+    14 -> BINLOG_USER_VAR_EVENT+    15 -> BINLOG_FORMAT_DESCRIPTION_EVENT+    16 -> BINLOG_XID_EVENT+    17 -> BINLOG_BEGIN_LOAD_QUERY_EVENT+    18 -> BINLOG_EXECUTE_LOAD_QUERY_EVENT+    19 -> BINLOG_TABLE_MAP_EVENT+    20 -> BINLOG_WRITE_ROWS_EVENTv0+    21 -> BINLOG_UPDATE_ROWS_EVENTv0+    22 -> BINLOG_DELETE_ROWS_EVENTv0+    23 -> BINLOG_WRITE_ROWS_EVENTv1+    24 -> BINLOG_UPDATE_ROWS_EVENTv1+    25 -> BINLOG_DELETE_ROWS_EVENTv1+    26 -> BINLOG_INCIDENT_EVENT+    27 -> BINLOG_HEARTBEAT_EVENT+    28 -> BINLOG_IGNORABLE_EVENT+    29 -> BINLOG_ROWS_QUERY_EVENT+    30 -> BINLOG_WRITE_ROWS_EVENTv2+    31 -> BINLOG_UPDATE_ROWS_EVENTv2+    32 -> BINLOG_DELETE_ROWS_EVENTv2+    33 -> BINLOG_GTID_EVENT+    34 -> BINLOG_ANONYMOUS_GTID_EVENT+    35 -> BINLOG_PREVIOUS_GTIDS_EVENT+    unrecognized -> BINLOG_UNRECOGNIZED_EVENT unrecognized++-- | The code this event type has in the binlog header.+binLogEventTypeCode :: BinLogEventType -> Word8+binLogEventTypeCode eventType = case eventType of+    BINLOG_UNKNOWN_EVENT -> 0+    BINLOG_START_EVENT_V3 -> 1+    BINLOG_QUERY_EVENT -> 2+    BINLOG_STOP_EVENT -> 3+    BINLOG_ROTATE_EVENT -> 4+    BINLOG_INTVAR_EVENT -> 5+    BINLOG_LOAD_EVENT -> 6+    BINLOG_SLAVE_EVENT -> 7+    BINLOG_CREATE_FILE_EVENT -> 8+    BINLOG_APPEND_BLOCK_EVENT -> 9+    BINLOG_EXEC_LOAD_EVENT -> 10+    BINLOG_DELETE_FILE_EVENT -> 11+    BINLOG_NEW_LOAD_EVENT -> 12+    BINLOG_RAND_EVENT -> 13+    BINLOG_USER_VAR_EVENT -> 14+    BINLOG_FORMAT_DESCRIPTION_EVENT -> 15+    BINLOG_XID_EVENT -> 16+    BINLOG_BEGIN_LOAD_QUERY_EVENT -> 17+    BINLOG_EXECUTE_LOAD_QUERY_EVENT -> 18+    BINLOG_TABLE_MAP_EVENT -> 19+    BINLOG_WRITE_ROWS_EVENTv0 -> 20+    BINLOG_UPDATE_ROWS_EVENTv0 -> 21+    BINLOG_DELETE_ROWS_EVENTv0 -> 22+    BINLOG_WRITE_ROWS_EVENTv1 -> 23+    BINLOG_UPDATE_ROWS_EVENTv1 -> 24+    BINLOG_DELETE_ROWS_EVENTv1 -> 25+    BINLOG_INCIDENT_EVENT -> 26+    BINLOG_HEARTBEAT_EVENT -> 27+    BINLOG_IGNORABLE_EVENT -> 28+    BINLOG_ROWS_QUERY_EVENT -> 29+    BINLOG_WRITE_ROWS_EVENTv2 -> 30+    BINLOG_UPDATE_ROWS_EVENTv2 -> 31+    BINLOG_DELETE_ROWS_EVENTv2 -> 32+    BINLOG_GTID_EVENT -> 33+    BINLOG_ANONYMOUS_GTID_EVENT -> 34+    BINLOG_PREVIOUS_GTIDS_EVENT -> 35+    BINLOG_UNRECOGNIZED_EVENT code -> code++data BinLogPacket = BinLogPacket+    { blTimestamp :: !Word32+    , blEventType :: !BinLogEventType+    , blServerId  :: !Word32+    , blEventSize :: !Word32+    , blLogPos    :: !Word64   -- ^ for future GTID compatibility+    , blFlags     :: !Word16+    , blBody      :: !L.ByteString+    , blSemiAck   :: !Bool+    } deriving (Show, Eq)++-- | @LOG_EVENT_IGNORABLE_F@: the server allows a reader that does not know+-- this event's type to skip it.+isIgnorableEvent :: BinLogPacket -> Bool+isIgnorableEvent packet = blFlags packet .&. 0x80 /= 0++putSemiAckResp :: Word32 -> ByteString -> Put+putSemiAckResp pos fn = put pos >> put fn++getBinLogPacket :: Bool -> Bool -> Get BinLogPacket+getBinLogPacket checksum semi = do+    _  <- getWord8     -- OK byte+    ack <- if semi+        then getWord8 >> (== 0x01) <$> getWord8+        else return False+    ts <- getWord32le+    typ <- binLogEventTypeFromCode <$> getWord8+    sid <- getWord32le+    size <- getWord32le+    pos <- getWord32le+    flgs <- getWord16le+    body <- getLazyByteString (fromIntegral size - if checksum then 23 else 19)+    return (BinLogPacket ts typ sid size (fromIntegral pos) flgs body ack)++getFromBinLogPacket :: Get a -> BinLogPacket -> IO a+getFromBinLogPacket g (BinLogPacket _ _ _ _ _ _ body _ ) =+    case parseDetailLazy g body  of+        Left  (buf, offset, errmsg) -> throwIO (DecodePacketFailed buf offset errmsg)+        Right (_,   _,      r     ) -> return r++getFromBinLogPacket' :: (BinLogEventType -> Get a) -> BinLogPacket -> IO a+getFromBinLogPacket' g (BinLogPacket _ typ _ _ _ _ body _ ) =+    case parseDetailLazy (g typ) body  of+        Left  (buf, offset, errmsg) -> throwIO (DecodePacketFailed buf offset errmsg)+        Right (_,   _,      r     ) -> return r++--------------------------------------------------------------------------------++data FormatDescription = FormatDescription+    { fdVersion              :: !Word16+    , fdMySQLVersion         :: !ByteString+    , fdCreateTime           :: !Word32+    -- , eventHeaderLen :: !Word8  -- const 19+    , fdEventHeaderLenVector :: !ByteString  -- ^ a array indexed by Binlog Event Type - 1+                                             -- to extract the length of the event specific header.+    } deriving (Show, Eq, Generic)++getFormatDescription :: Get FormatDescription+getFormatDescription = FormatDescription <$> getWord16le+                                         <*> getByteString 50+                                         <*> getWord32le+                                         <*  getWord8+                                         <*> (L.toStrict <$> getRemainingLazyByteString)++eventHeaderLen :: FormatDescription -> BinLogEventType -> Word8+eventHeaderLen fd typ+    | idx < 0 || idx >= B.length vec = 0+    | otherwise = B.unsafeIndex vec idx+  where+    vec :: ByteString+    vec = fdEventHeaderLenVector fd+    idx :: Int+    idx = Word8.toInt (binLogEventTypeCode typ) - 1++data RotateEvent = RotateEvent+    { rPos :: !Word64, rFileName :: !ByteString } deriving (Show, Eq)++getRotateEvent :: Get RotateEvent+getRotateEvent = RotateEvent <$> getWord64le <*> getRemainingByteString++-- | This's query parser for statement based binlog's query event, it's actually+-- not used in row based binlog.+--+data QueryEvent = QueryEvent+    { qSlaveProxyId :: !Word32+    , qExecTime     :: !Word32+    , qErrCode      :: !Word16+    , qStatusVars   :: !ByteString+    , qSchemaName   :: !ByteString+    , qQuery        :: !Query+    } deriving (Show, Eq, Generic)++getQueryEvent :: Get QueryEvent+getQueryEvent = do+    pid <- getWord32le+    tim <- getWord32le+    slen <- getWord8+    ecode <- getWord16le+    vlen <- getWord16le+    svar <- getByteString (fromIntegral vlen)+    schema <- getByteString (fromIntegral slen)+    _ <- getWord8+    qry <- getRemainingLazyByteString+    return (QueryEvent pid tim ecode svar schema (Query qry))++-- | This's the query event in row based binlog.+--+data QueryEvent' = QueryEvent' { qQuery' :: !Query } deriving (Show, Eq)++getQueryEvent' :: Get QueryEvent'+getQueryEvent' = do+    _ <- getWord8+    QueryEvent' . Query <$> getRemainingLazyByteString++data TableMapEvent = TableMapEvent+    { tmTableId    :: !Word64+    , tmFlags      :: !Word16+    , tmSchemaName :: !ByteString+    , tmTableName  :: !ByteString+    , tmColumnCnt  :: !Int+    , tmColumnType :: ![FieldType]+    , tmColumnMeta :: ![BinLogMeta]+    , tmNullMap    :: !ByteString+    } deriving (Show, Eq, Generic)++getTableMapEvent :: FormatDescription -> Get TableMapEvent+getTableMapEvent fd = do+    let hlen = eventHeaderLen fd BINLOG_TABLE_MAP_EVENT+    tid <- if hlen == 6 then fromIntegral <$> getWord32le else getWord48le+    flgs <- getWord16le+    slen <- getWord8+    schema <- getByteString (fromIntegral slen)+    _ <- getWord8 -- 0x00+    tlen <- getWord8+    table <- getByteString (fromIntegral tlen)+    _ <- getWord8 -- 0x00+    cc <- getLenEncInt+    colTypBS <- getByteString cc+    let typs = map FieldType (B.unpack colTypBS)+    colMetaBS <- getLenEncBytes++    metas <- case runGetOrFail (forM typs getBinLogMeta) (L.fromStrict colMetaBS) of+        Left (_, _, errmsg) -> fail errmsg+        Right (_, _, r)     -> return r++    nullmap <- getByteString ((cc + 7) `div` 8)+    return (TableMapEvent tid flgs schema table cc typs metas nullmap)++data DeleteRowsEvent = DeleteRowsEvent+    { deleteTableId    :: !Word64+    , deleteFlags      :: !Word16+    -- , deleteExtraData   :: !RowsEventExtraData+    , deleteColumnCnt  :: !Int+    , deletePresentMap :: !BitMap+    , deleteRowData    :: ![[BinLogValue]]+    } deriving (Show, Eq, Generic)++getDeleteRowEvent :: FormatDescription -> TableMapEvent -> BinLogEventType -> Get DeleteRowsEvent+getDeleteRowEvent fd tme typ = do+    let hlen = eventHeaderLen fd typ+    tid <- if hlen == 6 then fromIntegral <$> getWord32le else getWord48le+    flgs <- getWord16le+    when (typ == BINLOG_DELETE_ROWS_EVENTv2) $ do+        extraLen <- getWord16le+        void $ getByteString (fromIntegral extraLen - 2)+    colCnt <- getLenEncInt+    let (plen, poffset) = (colCnt + 7) `quotRem` 8+    pmap <- getPresentMap plen poffset+    DeleteRowsEvent tid flgs colCnt pmap <$> untilM (getBinLogRow (tmColumnMeta tme) pmap) isEmpty++data WriteRowsEvent = WriteRowsEvent+    { writeTableId    :: !Word64+    , writeFlags      :: !Word16+    -- , writeExtraData   :: !RowsEventExtraData+    , writeColumnCnt  :: !Int+    , writePresentMap :: !BitMap+    , writeRowData    :: ![[BinLogValue]]+    } deriving (Show, Eq, Generic)++getWriteRowEvent :: FormatDescription -> TableMapEvent -> BinLogEventType -> Get WriteRowsEvent+getWriteRowEvent fd tme typ = do+    let hlen = eventHeaderLen fd typ+    tid <- if hlen == 6 then fromIntegral <$> getWord32le else getWord48le+    flgs <- getWord16le+    when (typ == BINLOG_WRITE_ROWS_EVENTv2) $ do+        extraLen <- getWord16le+        void $ getByteString (fromIntegral extraLen - 2)+    colCnt <- getLenEncInt+    let (plen, poffset) = (colCnt + 7) `quotRem` 8+    pmap <- getPresentMap plen poffset+    WriteRowsEvent tid flgs colCnt pmap <$> untilM (getBinLogRow (tmColumnMeta tme) pmap) isEmpty++data UpdateRowsEvent = UpdateRowsEvent+    { updateTableId    :: !Word64+    , updateFlags      :: !Word16+    -- , updateExtraData   :: !RowsEventExtraData+    , updateColumnCnt  :: !Int+    , updatePresentMap :: !(BitMap, BitMap)+    , updateRowData    :: ![ ([BinLogValue], [BinLogValue]) ]+    } deriving (Show, Eq, Generic)++getUpdateRowEvent :: FormatDescription -> TableMapEvent -> BinLogEventType -> Get UpdateRowsEvent+getUpdateRowEvent fd tme typ = do+    let hlen = eventHeaderLen fd typ+    tid <- if hlen == 6 then fromIntegral <$> getWord32le else getWord48le+    flgs <- getWord16le+    when (typ == BINLOG_UPDATE_ROWS_EVENTv2) $ do+        extraLen <- getWord16le+        void $ getByteString (fromIntegral extraLen - 2)+    colCnt <- getLenEncInt+    let (plen, poffset) = (colCnt + 7) `quotRem` 8+    pmap <- getPresentMap plen poffset+    pmap' <- getPresentMap plen poffset+    UpdateRowsEvent tid flgs colCnt (pmap, pmap') <$>+        untilM ((,) <$> getBinLogRow (tmColumnMeta tme) pmap <*> getBinLogRow (tmColumnMeta tme) pmap')+               isEmpty++-- | The table ID that opens the body of a rows event of this type.+getRowsEventTableId :: FormatDescription -> BinLogEventType -> Get Word64+getRowsEventTableId fd typ =+    if eventHeaderLen fd typ == 6 then Word32.toWord64 <$> getWord32le else getWord48le++getPresentMap :: Int -> Int -> Get BitMap+getPresentMap plen poffset = do+    pmap <- getByteString plen+    let pmap' = if B.null pmap+                then B.empty+                else B.init pmap `B.snoc` (B.last pmap .&. 0xFF `shiftR` (7 - poffset))+    pure (BitMap pmap')+
+ src/Database/MySQL/BinLogProtocol/BinLogMeta.hs view
@@ -0,0 +1,119 @@+{-# OPTIONS_GHC -funbox-strict-fields #-}++{-|+Module      : Database.MySQL.BinLogProtocol.BinLogMeta+Description : Binlog protocol column meta+Copyright   : (c) Winterland, 2016+License     : BSD+Maintainer  : drkoster@qq.com+Stability   : experimental+Portability : PORTABLE++This module provide column meta decoder for binlog protocol.++There're certain type won't appear in binlog event, and some types are compressed into 'mySQLTypeString'+, please take python version as a reference:  <https://github.com/noplay/python-mysql-replication>++You will not directly meet following 'FieldType' namely:++    * mySQLTypeDecimal+    * mySQLTypeNewdate+    * mySQLTypeEnum+    * mySQLTypeSet+    * mySQLTypeTinyBlob+    * mySQLTypeMediumBlOb+    * mySQLTypeLongBlob++-}++module Database.MySQL.BinLogProtocol.BinLogMeta where++import           Data.Binary.Get+import           Data.Bits+import           Data.Word+import           Database.MySQL.Protocol.ColumnDef++-- | An intermedia date type for decoding row-based event's values.+--+data BinLogMeta+    = BINLOG_TYPE_TINY+    | BINLOG_TYPE_SHORT+    | BINLOG_TYPE_INT24+    | BINLOG_TYPE_LONG+    | BINLOG_TYPE_LONGLONG+    | BINLOG_TYPE_FLOAT       !Word8         -- ^ size+    | BINLOG_TYPE_DOUBLE      !Word8         -- ^ size+    | BINLOG_TYPE_BIT         !Word16 !Word8 -- ^ bits, bytes+    | BINLOG_TYPE_TIMESTAMP+    | BINLOG_TYPE_DATETIME+    | BINLOG_TYPE_DATE+    | BINLOG_TYPE_TIME+    | BINLOG_TYPE_TIMESTAMP2  !Word8         -- ^ fsp+    | BINLOG_TYPE_DATETIME2   !Word8         -- ^ fsp+    | BINLOG_TYPE_TIME2       !Word8         -- ^ fsp+    | BINLOG_TYPE_YEAR+    | BINLOG_TYPE_NEWDECIMAL  !Word8 !Word8  -- ^ precision, scale+    | BINLOG_TYPE_ENUM        !Word8         -- ^ 1 or 2('Word8' or 'Word16'), enum index size+    | BINLOG_TYPE_SET         !Word16 !Word8 -- ^ bitmap bits, bytes+    | BINLOG_TYPE_BLOB        !Word8         -- ^ length size+    | BINLOG_TYPE_STRING      !Word16        -- ^ meta length(if < 256, then length is 8bit,+                                             -- if > 256 then length is 16bit)+    | BINLOG_TYPE_GEOMETRY    !Word8         -- ^ length size+  deriving (Show, Eq)++getBinLogMeta :: FieldType -> Get BinLogMeta+getBinLogMeta t+    | t == mySQLTypeTiny       = pure BINLOG_TYPE_TINY+    | t == mySQLTypeShort      = pure BINLOG_TYPE_SHORT+    | t == mySQLTypeInt24      = pure BINLOG_TYPE_INT24+    | t == mySQLTypeLong       = pure BINLOG_TYPE_LONG+    | t == mySQLTypeLongLong   = pure BINLOG_TYPE_LONGLONG+    | t == mySQLTypeFloat      = BINLOG_TYPE_FLOAT <$> getWord8+    | t == mySQLTypeDouble     = BINLOG_TYPE_DOUBLE <$> getWord8++    | t == mySQLTypeBit        = do+        byte0 <- getWord8+        byte1 <- getWord8+        let nbits = (fromIntegral byte1 `shiftL` 3) .|.  fromIntegral byte0+            nbytes = fromIntegral $ (nbits + 7) `shiftR` 3+        pure (BINLOG_TYPE_BIT nbits nbytes)++    | t == mySQLTypeTimestamp  = pure BINLOG_TYPE_TIMESTAMP+    | t == mySQLTypeDateTime   = pure BINLOG_TYPE_DATETIME+    | t == mySQLTypeDate       = pure BINLOG_TYPE_DATE+    | t == mySQLTypeTime       = pure BINLOG_TYPE_TIME+    | t == mySQLTypeTimestamp2 = BINLOG_TYPE_TIMESTAMP2 <$> getWord8+    | t == mySQLTypeDateTime2  = BINLOG_TYPE_DATETIME2 <$> getWord8+    | t == mySQLTypeTime2      = BINLOG_TYPE_TIME2 <$> getWord8+    | t == mySQLTypeYear       = pure BINLOG_TYPE_YEAR+    | t == mySQLTypeNewDecimal = BINLOG_TYPE_NEWDECIMAL <$> getWord8 <*> getWord8+    | t == mySQLTypeVarChar    = BINLOG_TYPE_STRING <$> getWord16le+    | t == mySQLTypeVarString  = BINLOG_TYPE_STRING <$> getWord16le++    | t == mySQLTypeString     = do+        byte0 <- getWord8+        byte1 <- getWord8+        -- http://bugs.mysql.com/37426+        if  byte0 > 0+        then if (byte0 .&. 0x30) /= 0x30+             then if FieldType (byte0 .|. 0x30) == mySQLTypeString+                  then let len = fromIntegral $ (byte0 .&. 0x30) `xor` 0x30+                           len' = len `shiftL` 4 .|. fromIntegral byte1+                       in pure $! BINLOG_TYPE_STRING len'+                  else let len = fromIntegral byte0 `shiftL` 8 :: Word16+                           len' = len .|. fromIntegral byte1+                       in pure $! BINLOG_TYPE_STRING len'+             else let t' = FieldType byte0+                  in if | t' == mySQLTypeSet    -> let nbits = fromIntegral byte1 `shiftL` 3+                                                       nbytes = fromIntegral $ (nbits + 7) `shiftR` 8+                                                   in pure (BINLOG_TYPE_SET nbits nbytes)+                        | t' == mySQLTypeEnum   -> pure (BINLOG_TYPE_ENUM byte1)+                        | t' == mySQLTypeString -> pure (BINLOG_TYPE_STRING (fromIntegral byte1))+                        | otherwise             -> fail $ "Database.MySQL.BinLogProtocol.BinLogMeta:\+                                                           \ impossible type inside binlog string: " ++ show t'+        else pure (BINLOG_TYPE_STRING (fromIntegral byte1))++    | t == mySQLTypeBlob       = BINLOG_TYPE_BLOB <$> getWord8+    | t == mySQLTypeGeometry   = BINLOG_TYPE_GEOMETRY <$> getWord8+    | otherwise                = fail $ "Database.MySQL.BinLogProtocol.BinLogMeta:\+                                        \ impossible type in binlog: " ++ show t
+ src/Database/MySQL/BinLogProtocol/BinLogValue.hs view
@@ -0,0 +1,325 @@+{-|+Module      : Database.MySQL.BinLogProtocol.BinLogValue+Description : Binlog protocol+Copyright   : (c) Winterland, 2016+License     : BSD+Maintainer  : drkoster@qq.com+Stability   : experimental+Portability : PORTABLE++Binlog protocol++-}++module Database.MySQL.BinLogProtocol.BinLogValue where++import           Data.Binary.Get+import           Data.Binary.Put                          ()+import           Data.Bits+import           Data.ByteString                          (ByteString)+import qualified Data.ByteString                          as B+import qualified Data.ByteString.Unsafe                   as B+import           Data.Int+import           Data.Int.Int24+import           Data.Scientific+import           Data.Word+import           Database.MySQL.BinLogProtocol.BinLogMeta+import           Database.MySQL.Protocol.MySQLValue+import           Database.MySQL.Protocol.Packet+import           GHC.Generics                             (Generic)++-- | Data type for representing binlog values.+--+-- This data type DOES NOT try to parse binlog values into detailed haskell values,+-- because you may not want to waste performance in situations like database middleware.+--+-- Due to the lack of signedness infomation in binlog meta, we cannot distinguish,+-- for example, between unsigned tiny 255 and tiny -1, so we use int to present+-- @TINY,SHORT,INT,LONG@. If you have unsigned columns, use 'fromIntegral' to convert it+-- to word to get real unsigned value back, for example, @fromIntegral (-1 :: Int) == 255 :: Word@+--+-- For above reason, we use 'Int24' to present MySQL's @INT24@ type, you can get back the+-- unsigned value using @word24@ package's 'Word24' type.+--+-- Timestamp types('BinLogTimeStamp' and 'BinLogTimeStamp2') are values converted into UTC already,+-- see 'MySQLVaule' 's note.+--+-- There's also no infomation about charset, so we use 'ByteString' to present both text+-- and blob types, if you want to get text representation back, you have to query column charset+-- infomation, and use icu or iconv to decode. IT MAY NOT BE UTF-8.+--+-- The @SET@ and @ENUM@ values are presented by their index's value and bitmap respectively,+-- if you need get the string value back, you have to perform a 'DESC tablename' to get the+-- set or enum table.+--+data BinLogValue+    = BinLogTiny       !Int8+    | BinLogShort      !Int16+    | BinLogInt24      !Int24+    | BinLogLong       !Int32+    | BinLogLongLong   !Int64+    | BinLogFloat      !Float+    | BinLogDouble     !Double+    | BinLogBit        !Word64          -- ^ a 64bit bitmap.+    | BinLogTimeStamp  !Word32          -- ^ a utc timestamp, note 0 doesn't mean @1970-01-01 00:00:00@,+                                        -- because mysql choose 0 to present '0000-00-00 00:00:00'+    | BinLogTimeStamp2 !Word32 !Word32  -- ^ like 'BinLogTimeStamp' with an addtional microseconds field.+    | BinLogDateTime   !Word16 !Word8 !Word8 !Word8 !Word8 !Word8         -- ^ YYYY MM DD hh mm ss+    | BinLogDateTime2  !Word16 !Word8 !Word8 !Word8 !Word8 !Word8 !Word32 -- ^ YYYY MM DD hh mm ss microsecond+    | BinLogDate       !Word16 !Word8 !Word8                   -- ^ YYYY MM DD+    | BinLogTime       !Word8  !Word16 !Word8 !Word8           -- ^ sign(1= non-negative, 0= negative) hh mm ss+    | BinLogTime2      !Word8  !Word16 !Word8 !Word8 !Word32   -- ^ sign(1= non-negative, 0= negative) hh mm ss microsecond+    | BinLogYear       !Word16                                 -- ^ year value, 0 stand for '0000'+    | BinLogNewDecimal !Scientific                             -- ^ sign(1= non-negative, 0= negative) integeral part, fractional part+    | BinLogEnum       !Word16                                 -- ^ enum indexing value+    | BinLogSet        !Word64                                 -- ^ set indexing 64bit bitmap.+    | BinLogBytes      !ByteString                             -- ^ all string and blob values.+    | BinLogGeometry   !ByteString+    | BinLogNull+  deriving (Show, Eq, Generic)++--------------------------------------------------------------------------------+-- | BinLog protocol decoder+--+getBinLogField :: BinLogMeta -> Get BinLogValue+getBinLogField BINLOG_TYPE_TINY                = BinLogTiny     <$> getInt8+getBinLogField BINLOG_TYPE_SHORT               = BinLogShort    <$> getInt16le+getBinLogField BINLOG_TYPE_INT24               = BinLogInt24 . fromIntegral <$> getWord24le+getBinLogField BINLOG_TYPE_LONG                = BinLogLong     <$> getInt32le+getBinLogField BINLOG_TYPE_LONGLONG            = BinLogLongLong <$> getInt64le+getBinLogField (BINLOG_TYPE_FLOAT  _         ) = BinLogFloat <$> getFloatle+getBinLogField (BINLOG_TYPE_DOUBLE _         ) = BinLogDouble <$> getDoublele+getBinLogField (BINLOG_TYPE_BIT    _    bytes) = BinLogBit <$> getBits' bytes+getBinLogField BINLOG_TYPE_TIMESTAMP           = BinLogTimeStamp <$> getWord32le++-- A integer in @YYYYMMDD@ format, for example:+-- 99991231 stand for @9999-12-31@+getBinLogField BINLOG_TYPE_DATE = do+    i <- getWord24le+    let (i', dd) = i `quotRem` 32+        (yyyy, mm) = i' `quotRem` 16+    pure (BinLogDate (fromIntegral yyyy)+                     (fromIntegral mm)+                     (fromIntegral dd))++getBinLogField (BINLOG_TYPE_TIMESTAMP2  fsp) = do+    s <- getWord32be -- big-endian here!+    ms <- fromIntegral <$> getMicroSecond fsp+    pure (BinLogTimeStamp2 s ms)++-- A integer in @YYYYMMDDhhmmss@, for example:+-- 99991231235959 stand for @9999-12-31 23:59:59@+getBinLogField BINLOG_TYPE_DATETIME = do+    i <- getWord64le+    let (yyyy, i')   = i      `quotRem` 10000000000+        (mm, i'')    = i'     `quotRem` 100000000+        (dd, i''')   = i''    `quotRem` 1000000+        (h, i'''')   = i'''   `quotRem` 10000+        (m, s)       = i''''  `quotRem` 100+    pure (BinLogDateTime (fromIntegral yyyy)+                         (fromIntegral mm)+                         (fromIntegral dd)+                         (fromIntegral h)+                         (fromIntegral m)+                         (fromIntegral s))++-- BINLOG_TYPE_DATETIME2(big endian)+--+-- 1 bit sign (used when on disk)+-- 17 bits year * 13 + month (year 0-9999, month 0-12)+-- 5 bits day (0-31)+-- 5 bits hour (0-23)+-- 6 bits minute (0-59)+-- 6 bits second (0-59)+-- (5 bytes in total)+--+-- fractional-seconds storage (size depends on meta)+--+getBinLogField (BINLOG_TYPE_DATETIME2 fsp) = do+    iPart <- getWord40be+    let yyyymm = iPart `shiftR` 22 .&. 0x01FFFF -- 0b011111111111111111+        (yyyy, mm) = yyyymm `quotRem` 13+        yyyy' = fromIntegral yyyy+        mm' = fromIntegral mm+        dd = fromIntegral $ iPart `shiftR` 17 .&. 0x1F -- 0b00011111+        h =  fromIntegral $ iPart `shiftR` 12 .&. 0x1F -- 0b00011111+        m =  fromIntegral $ iPart `shiftR` 6 .&. 0x3F  -- 0b00111111+        s =  fromIntegral $ iPart .&. 0x3F             -- 0b00111111+    ms <- fromIntegral <$> getMicroSecond fsp+    pure (BinLogDateTime2 yyyy' mm' dd h m s ms)++-- A integer in @hhmmss@ format(can be negative), for example:+-- 8385959 stand for @838:59:59@+getBinLogField BINLOG_TYPE_TIME = do+    i <- getWord24le+    let i' =  fromIntegral i :: Int24+        sign = if i' >= 0 then 1 else 0+    let (h, i'')     = i'     `quotRem` 10000+        (m, s)       = i''    `quotRem` 100+    pure (BinLogTime sign (fromIntegral (abs h))+                          (fromIntegral (abs m))+                          (fromIntegral (abs s)))++-- BINLOG_TYPE_TIME2(big endian)+--+-- 1 bit sign  (1= non-negative, 0= negative)+-- 1 bit unused (Reserved for wider hour range, e.g. for intervals)+-- 10 bit hour (0-836)+-- 6 bit minute (0-59)+-- 6 bit second (0-59)+-- (3 bytes in total)+--+-- fractional-seconds storage (size depends on meta)+--+getBinLogField (BINLOG_TYPE_TIME2 fsp) = do+    iPart <- getWord24be+    let sign = fromIntegral $ iPart `shiftR` 23+        iPart' = if sign == 0 then 0x800000 - iPart - 1 else iPart+        h = fromIntegral (iPart' `shiftR` 12) .&. 0x03FF -- 0b0000001111111111+        m = fromIntegral (iPart' `shiftR` 6) .&. 0x3F    -- 0b00111111+        s = fromIntegral iPart' .&. 0x3F               -- 0b00111111+    ms <- abs <$> getMicroSecond fsp+    let ms' = abs (fromIntegral ms :: Int)+    pure (BinLogTime2 sign h m s (fromIntegral ms'))++getBinLogField BINLOG_TYPE_YEAR                = do+    y <- getWord8+    pure $! if y == 0 then BinLogYear 0 else BinLogYear (1900 + fromIntegral y)++-- Decimal representation in binlog seems to be as follows:+--+-- 1st bit - sign such that set == +, unset == -+-- every 4 bytes represent 9 digits in big-endian order.+--+-- 80 00 00 05 1b 38 b0 60 00 means:+--+--   0x80 - positive+--   0x00000005 - 5+--   0x1b38b060 - 456700000+--   0x00       - 0+--+-- 54567000000 / 10^{10} = 5.4567+--+-- if there're < 9 digits at first, it will be compressed into suitable length words+-- following a simple lookup table.+--+getBinLogField (BINLOG_TYPE_NEWDECIMAL precision scale)+    | precision < scale = fail "NEWDECIMAL: precision must be >= scale"+    | otherwise = do+    let i = (precision - scale)+        (ucI, cI) = i `quotRem` digitsPerInteger+        (ucF, cF) = scale `quotRem` digitsPerInteger+        ucISize = fromIntegral (ucI `shiftL` 2)+        ucFSize = fromIntegral (ucF `shiftL` 2)+        cISize = fromIntegral (sizeTable `B.unsafeIndex` fromIntegral cI)+        cFSize = fromIntegral (sizeTable `B.unsafeIndex` fromIntegral cF)+        len = ucISize + cISize + ucFSize + cFSize++    buf <- getByteString len++    let fb = buf `B.unsafeIndex` 0+        sign = if fb .&. 0x80 == 0x80 then 1 else 0 :: Word8+        buf' = (fb `xor` 0x80) `B.cons` B.tail buf+        buf'' = if sign == 1 then buf'+                            else B.map (xor 0xFF) buf'++        iPart = fromIntegral (getCompressed cISize (B.unsafeTake cISize buf'')) * (blockSize ^ ucI)+              + getUncompressed ucI (B.unsafeDrop cISize buf'')++    let buf''' = B.unsafeDrop (ucISize + cISize) buf''++        fPart = getUncompressed ucF (B.unsafeTake ucFSize buf''') * (10 ^ cF)+              + fromIntegral (getCompressed cFSize (B.unsafeDrop ucFSize buf'''))++    let sci = scientific (iPart * 10 ^ scale + fPart) (negate $ fromIntegral scale)+        sci' = if sign == 0 then negate sci else sci+    pure (BinLogNewDecimal sci')+  where+    digitsPerInteger = 9+    blockSize = fromIntegral $ (10 :: Int32) ^ (9 :: Int)+    sizeTable = B.pack [0, 1, 1, 2, 2, 3, 3, 4, 4, 4]++    getCompressed :: Int -> ByteString -> Word64+    getCompressed 0 _  = 0+    getCompressed x bs = let fb = bs `B.unsafeIndex` 0+                             x' = x - 1+                         in fromIntegral fb `shiftL` (8 * x') .|. getCompressed x' (B.unsafeDrop 1 bs)++    getUncompressed :: Word8 -> ByteString -> Integer+    getUncompressed 0 _ = 0+    getUncompressed x bs = let v = getCompressed 4 (B.unsafeTake 4 bs)+                               x' = x - 1+                           in fromIntegral v * (blockSize ^ x') + getUncompressed x' (B.unsafeDrop 4 bs)+++getBinLogField (BINLOG_TYPE_ENUM size) =+    if  | size == 1 -> BinLogEnum . fromIntegral <$> getWord8+        | size == 2 -> BinLogEnum <$> getWord16be+        | otherwise -> fail $ "Database.MySQL.BinLogProtocol.BinLogValue: wrong \+                              \BINLOG_TYPE_ENUM size: " ++ show size+++getBinLogField (BINLOG_TYPE_SET _ bytes) = BinLogSet <$> getBits' bytes+getBinLogField (BINLOG_TYPE_BLOB lensize) = do+    len <- if  | lensize == 1 -> fromIntegral <$> getWord8+               | lensize == 2 -> fromIntegral <$> getWord16le+               | lensize == 3 -> fromIntegral <$> getWord24le+               | lensize == 4 -> fromIntegral <$> getWord32le+               | otherwise    -> fail $ "Database.MySQL.BinLogProtocol.BinLogValue: \+                                        \wrong BINLOG_TYPE_BLOB length size: " ++ show lensize+    BinLogBytes <$> getByteString len++getBinLogField (BINLOG_TYPE_STRING size) = do+    len <- if | size < 256 -> fromIntegral <$> getWord8+              | otherwise  -> fromIntegral <$> getWord16le+    BinLogBytes <$> getByteString len++getBinLogField (BINLOG_TYPE_GEOMETRY lensize) = do+    len <- if | lensize == 1 -> fromIntegral <$> getWord8+              | lensize == 2 -> fromIntegral <$> getWord16le+              | lensize == 3 -> fromIntegral <$> getWord24le+              | lensize == 4 -> fromIntegral <$> getWord32le+              | otherwise    -> fail $  "Database.MySQL.BinLogProtocol.BinLogValue: \+                                        \wrong BINLOG_TYPE_GEOMETRY length size: " ++ show lensize+    BinLogGeometry <$> getByteString len++getMicroSecond :: Word8 -> Get Int32+getMicroSecond 0 = pure 0+getMicroSecond 1 = (* 100000) . fromIntegral <$> getInt8+getMicroSecond 2 = (* 10000) . fromIntegral <$> getInt8+getMicroSecond 3 = (* 1000) . fromIntegral <$> getInt16be+getMicroSecond 4 = (* 100) . fromIntegral <$> getInt16be+getMicroSecond 5 = (* 10) . fromIntegral <$> getInt24be+getMicroSecond 6 = fromIntegral <$> getInt24be+getMicroSecond _ = pure 0++getBits' :: Word8 -> Get Word64+getBits' bytes = if bytes <= 8+    then getBits (fromIntegral bytes)+    else fail $  "Database.MySQL.BinLogProtocol.BinLogValue: \+                 \wrong bit length size: " ++ show bytes++--------------------------------------------------------------------------------+-- | BinLog row decoder+--+getBinLogRow :: [BinLogMeta] -> BitMap -> Get [BinLogValue]+getBinLogRow metas pmap = do+    let plen = B.foldl' (\acc word8 -> acc + popCount word8) 0 (fromBitMap pmap)+        maplen = (plen + 7) `shiftR` 3+    nullmap <- getByteString maplen+    go metas (BitMap nullmap) 0 pmap 0+  where+    go :: [BinLogMeta] -> BitMap -> Int -> BitMap -> Int -> Get [BinLogValue]+    go []     _       _       _     _    = pure []+    go (f:fs) nullmap nullpos pmap' ppos = do+        let ppos' = ppos + 1+        if isColumnSet pmap' ppos+        then do+            r <- if isColumnSet nullmap nullpos+                    then return BinLogNull+                    else getBinLogField f+            let nullpos' = nullpos + 1+            rest <- nullpos' `seq` ppos' `seq` go fs nullmap nullpos' pmap' ppos'+            return (rest `seq` (r : rest))+        else ppos' `seq` go fs nullmap nullpos pmap' ppos'+
+ src/Database/MySQL/BinLogProtocol/CRC32.hs view
@@ -0,0 +1,53 @@+{-|+Module      : Database.MySQL.BinLogProtocol.CRC32+Description : The checksum of binlog events+License     : BSD+Maintainer  : hi@jappie.me+Stability   : experimental+Portability : PORTABLE++The CRC32 that MySQL and MariaDB end each binlog event with when+@binlog_checksum@ is @CRC32@.+-}++module Database.MySQL.BinLogProtocol.CRC32+    ( crc32+    ) where++import           Data.Array.Unboxed    (UArray, listArray, (!))+import           Data.Bits             (complement, shiftR, xor, (.&.))+import qualified Data.ByteString.Lazy  as L+import           Data.Word             (Word32, Word8)+import qualified Unwitch.Convert.Word8 as Word8++-- Decision: our own CRC32, as no dependency has one: crypton and ram offer+-- cryptographic and FNV/SipHash digests only, and zlib, whose Haskell binding+-- exports no crc32 anyway, is not a dependency. The digest package would add+-- a C library for these few lines.++-- | The CRC-32 of IEEE 802.3 and zlib (reflected polynomial 0xEDB88320,+-- initial and final XOR 0xFFFFFFFF): @crc32 "123456789" == 0xCBF43926@.+--+-- @since 1.5.0+crc32 :: L.ByteString -> Word32+crc32 bytes = complement (L.foldl' crc32Step 0xFFFFFFFF bytes)++crc32Step :: Word32 -> Word8 -> Word32+crc32Step crc byte =+    (crc32Table ! ((crc `xor` Word8.toWord32 byte) .&. 0xFF)) `xor` (crc `shiftR` 8)++-- Decision: a UArray indexed by Word32, so a lookup takes the masked CRC as+-- it is. A Vector needs an Int index, and unwitch converts Word32 to Int only+-- as a Maybe, whose Nothing could not happen here but would need handling.++-- | The CRC of each byte value, so that a byte costs one lookup instead of+-- eight shifts.+crc32Table :: UArray Word32 Word32+crc32Table = listArray (0, 255) (map crc32OfByte [0 .. 255])++crc32OfByte :: Word32 -> Word32+crc32OfByte byte = iterate reflectedShift byte !! 8++reflectedShift :: Word32 -> Word32+reflectedShift crc =+    if crc .&. 1 /= 0 then 0xEDB88320 `xor` (crc `shiftR` 1) else crc `shiftR` 1
+ src/Database/MySQL/Connection.hs view
@@ -0,0 +1,772 @@++{-|+Module      : Database.MySQL.Connection+Description : Connection managment+Copyright   : (c) Winterland, 2016+License     : BSD+Maintainer  : drkoster@qq.com+Stability   : experimental+Portability : PORTABLE++This is an internal module, the 'MySQLConn' type should not directly acessed to user.++-}++module Database.MySQL.Connection+    ( -- * Connections+      MySQLConn (..)+    , ReplyState (..)+    , ConnectInfo (..)+    , defaultConnectInfo+    , defaultConnectInfoMB4+    , utf8_general_ci+    , utf8mb4_unicode_ci+    , bUFSIZE+    , connect+    , connectDetail+    , connectUnixSocket+    , connectUnixSocketDetail+    , connectDetailExtended+    , connectUnixSocketDetailExtended+    , withoutNegotiated+    , authenticatedConnection+      -- * Authentication+    , mkAuth+    , handshakeResponsePacket+    , sslRequestPacket+    , scrambleForPlugin+    , scrambleSHA1+    , scrambleSHA256+    , scrambleEd25519+    , ed25519PublicKey+    , completeAuth+    , plainFullAuth+    , parseRSAPublicKey+    , xorPasswordNonce+    , doRSAAuth+    , AuthException (..)+      -- * Commands and replies+    , decodeInputStream+    , close+    , ping+    , resetConnection+    , command+    , waitCommandReply+    , waitCommandReplys+    , readPacket+    , writeCommand+    , writePayload+    , sendPacket+    , guardUnconsumed+    , runCommand+    , settleFailedExchange+    , endsReply+    , writeIORef'+      -- * Exceptions+    , NetworkException (..)+    , UnconsumedResultSet (..)+    , AbandonedReply (..)+    , ERRException (..)+    , UnexpectedPacket (..)+    , ExtraResultSets (..)+    ) where++import           Control.Exception               (Exception (displayException),+                                                  bracketOnError, fromException,+                                                  mask, throwIO, catch, SomeException)+import           Control.Monad+import qualified Crypto.ECC.Edwards25519         as Ed+import           Crypto.Error                    (CryptoError, CryptoFailable, eitherCryptoError)+import qualified Crypto.Hash                     as Crypto+import           Crypto.Hash.Algorithms          (SHA1(..))+import qualified Crypto.Number.Serialize         as Serialize+import qualified Crypto.PubKey.RSA               as RSA+import qualified Crypto.PubKey.RSA.OAEP          as OAEP+import qualified Data.ASN1.BinaryEncoding        as ASN1B+import qualified Data.ASN1.BitArray              as ASN1BA+import qualified Data.ASN1.Encoding              as ASN1E+import qualified Data.ASN1.Types                 as ASN1+import qualified Data.Binary                     as Binary+import qualified Data.Binary.Put                 as Binary+import           Data.Bits+import qualified Data.ByteArray                  as BA+import           Data.ByteString                 (ByteString)+import qualified Data.ByteString                 as B+import qualified Data.ByteString.Lazy            as L+import qualified Data.ByteString.Unsafe          as B+import           Data.IORef                      (IORef, newIORef, readIORef,+                                                  writeIORef)+import           Data.Maybe                      (isJust)+import qualified Data.PEM                        as PEM+import           Data.Typeable+import           Data.Int                        (Int64)+import           Data.Word+import           Database.MySQL.Protocol.Auth+import           Database.MySQL.Protocol.Command+import           Database.MySQL.Protocol.Packet+import           Network.Socket                  (HostName, PortNumber)+import qualified Network.Socket                  as N+import           System.IO.Streams               (InputStream)+import qualified System.IO.Streams               as Stream+import qualified System.IO.Streams.TCP           as TCP+import qualified Data.Connection                 as TCP++--------------------------------------------------------------------------------++-- | 'MySQLConn' wrap both 'InputStream' and 'OutputStream' for MySQL 'Packet'.+--+-- You shouldn't use one 'MySQLConn' in different thread, if you do that,+-- consider protecting it with a @MVar@.+--+data MySQLConn = MySQLConn {+        mysqlRead        :: {-# UNPACK #-} !(InputStream  Packet)+    ,   mysqlWrite       :: (Packet -> IO ())+    ,   mysqlCloseSocket :: IO ()+    ,   replyState       :: {-# UNPACK #-} !(IORef ReplyState)+    }++-- | How far the replies to the commands sent on a connection have been read.+-- See Note [Abandoned replies].+--+-- @since 1.4.0+data ReplyState+    = RepliesRead+      -- ^ Every reply was read, so a command may be sent.+    | RowsUnread+      -- ^ A result set's rows are still to be read from its stream.+    | ReplyAbandoned+      -- ^ Reading a reply stopped part-way; the connection refuses commands.+    deriving (Show, Eq)++-- Note [Abandoned replies]+-- ~~~~~~~~~~~~~~~~~~~~~~~~+-- The server answers commands in order on one socket, so a reply left unread+-- is read as the reply to the next command. A timeout around query_ used to+-- do that: interrupted before it read the column count, it left the+-- connection looking idle, and the next SELECT returned the earlier one's+-- rows (issue #10).+--+-- Decision: a command's exchange runs with the state at ReplyAbandoned until+-- its reply is read, so an exception part-way leaves the connection refusing+-- commands with AbandonedReply, and the caller reconnects. Draining the rest+-- of the reply instead would block on the server for as long as the+-- interrupted statement runs, which defeats the timeout. ERRException and+-- ExtraResultSets are only raised after a reply was read in full, so they+-- leave the connection usable, as does a DecodePacketException on a row,+-- whose packet was read before it failed to decode.++-- | Everything you need to establish a MySQL connection.+--+-- To setup a TLS connection, use module "Database.MySQL.TLS" or "Database.MySQL.OpenSSL".+--+data ConnectInfo = ConnectInfo+    { ciHost     :: HostName+    , ciPort     :: PortNumber+    , ciDatabase :: ByteString+    , ciUser     :: ByteString+    , ciPassword :: ByteString+    , ciCharset  :: Word8+    } deriving Show++-- | A simple 'ConnectInfo' targeting localhost with @user=root@ and empty password.+--+--  Default charset is set to @utf8_general_ci@ to support older(< 5.5.3) MySQL versions,+--  but be aware this is a partial utf8 encoding, you may want to use 'defaultConnectInfoMB4'+--  instead to support full utf8 charset(emoji, etc.). You can query your server's support+--  with @SELECT id, collation_name FROM information_schema.collations ORDER BY id;@+--+defaultConnectInfo :: ConnectInfo+defaultConnectInfo = ConnectInfo "127.0.0.1" 3306 "" "root" "" utf8_general_ci++-- | 'defaultConnectInfo' with charset set to @utf8mb4_unicode_ci@+--+-- This is recommanded on any MySQL server version >= 5.5.3.+--+defaultConnectInfoMB4 :: ConnectInfo+defaultConnectInfoMB4 = ConnectInfo "127.0.0.1" 3306 "" "root" "" utf8mb4_unicode_ci++utf8_general_ci :: Word8+utf8_general_ci = 33++utf8mb4_unicode_ci :: Word8+utf8mb4_unicode_ci = 224++--------------------------------------------------------------------------------++-- | Socket buffer size.+--+-- maybe exposed to 'ConnectInfo' laster?+--+bUFSIZE :: Int+bUFSIZE = 16384++-- | Establish a MySQL connection.+--+connect :: ConnectInfo -> IO MySQLConn+connect = fmap snd . connectDetail++-- | Establish a MySQL connection with 'Greeting' back, so you can find server's version .etc.+--+connectDetail :: ConnectInfo -> IO (Greeting, MySQLConn)+connectDetail = fmap withoutNegotiated . connectDetailExtended noExtendedCapabilities++-- | 'connectDetail' asking a MariaDB server for extended capabilities, also+-- returning those the server agreed to. Asking for none gives exactly+-- 'connectDetail'. See Note [MariaDB extended capabilities].+--+-- @since 1.4.0+connectDetailExtended :: ExtendedCapabilities -> ConnectInfo -> IO (Greeting, ExtendedCapabilities, MySQLConn)+connectDetailExtended requested ci =+    bracketOnError+        (TCP.connectSocket (ciHost ci) (ciPort ci) >>= TCP.socketToConnection bUFSIZE)+        TCP.close+        (handshakePlain requested ci)++-- | Connect to MySQL via a Unix domain socket.+--+connectUnixSocket :: FilePath -> ConnectInfo -> IO MySQLConn+connectUnixSocket socketPath ci = fmap snd (connectUnixSocketDetail socketPath ci)++-- | Connect to MySQL via a Unix domain socket with 'Greeting' back,+-- so you can find server's version .etc.+--+connectUnixSocketDetail :: FilePath -> ConnectInfo -> IO (Greeting, MySQLConn)+connectUnixSocketDetail socketPath =+    fmap withoutNegotiated . connectUnixSocketDetailExtended noExtendedCapabilities socketPath++-- | 'connectUnixSocketDetail' asking for extended capabilities, as+-- 'connectDetailExtended' does.+--+-- @since 1.4.0+connectUnixSocketDetailExtended :: ExtendedCapabilities -> FilePath -> ConnectInfo+                                -> IO (Greeting, ExtendedCapabilities, MySQLConn)+connectUnixSocketDetailExtended requested socketPath ci =+    bracketOnError (openUnixSocket socketPath) TCP.close (handshakePlain requested ci)++openUnixSocket :: FilePath -> IO TCP.TCPConnection+openUnixSocket socketPath = bracketOnError+    (N.socket N.AF_UNIX N.Stream 0)+    N.close+    (\sock -> do+        N.connect sock (N.SockAddrUnix socketPath)+        TCP.socketToConnection bUFSIZE (sock, N.SockAddrUnix socketPath)+    )++withoutNegotiated :: (Greeting, ExtendedCapabilities, MySQLConn) -> (Greeting, MySQLConn)+withoutNegotiated (greet, _, conn) = (greet, conn)++-- | Authenticate over a connection without TLS, TCP or Unix socket alike.+handshakePlain :: ExtendedCapabilities -> ConnectInfo -> TCP.TCPConnection+               -> IO (Greeting, ExtendedCapabilities, MySQLConn)+handshakePlain requested (ConnectInfo _host _port db user pass charset) c = do+    is' <- decodeInputStream (TCP.source c)+    p <- readPacket is'+    (greet, offered) <- getFromPacket getGreetingExtended p+    let salt = greetingSalt1 greet `B.append` greetingSalt2 greet+        negotiated = negotiateExtended requested greet offered+    sendPacket c (handshakeResponsePacket 1 negotiated db user pass charset greet)+    q <- readPacket is'+    completeAuth is' (sendPacket c) pass q (plainFullAuth salt)+    conn <- authenticatedConnection is' c+    return (greet, negotiated, conn)++-- | The 'MySQLConn' over a connection whose handshake completed.+--+-- @since 1.4.0+authenticatedConnection :: InputStream Packet -> TCP.Connection a -> IO MySQLConn+authenticatedConnection is' c = do+    state <- newIORef RepliesRead+    return $ MySQLConn is'+        (sendPacket c)+        (writeCommand COM_QUIT (sendPacket c) >> quitReply is' >> TCP.close c)+        state++-- | After COM_QUIT the server either replies an OK packet or closes the+-- connection, so a failed read is not an error here.+quitReply :: InputStream Packet -> IO ()+quitReply is' = catch+    (void (waitCommandReply is'))+    ((\_ -> return ()) :: SomeException -> IO ())++sendPacket :: TCP.Connection a -> Packet -> IO ()+sendPacket c = TCP.send c . Binary.runPut . Binary.put++-- | The handshake response for a connection using the negotiated extended+-- capabilities, numbered @seqN@ (1 after a plain greeting, 2 after an SSL+-- request). With 'noExtendedCapabilities' it is the packet mysql-haskell sent+-- before extended capabilities existed.+--+-- @since 1.4.0+handshakeResponsePacket :: Word8 -> ExtendedCapabilities -> ByteString -> ByteString -> ByteString+                        -> Word8 -> Greeting -> Packet+handshakeResponsePacket seqN negotiated db user pass charset greet =+    putToPacket seqN (putAuthExtended negotiated (mkAuthFor negotiated db user pass charset greet))++-- | The SSL request for a connection using the negotiated extended+-- capabilities, as 'handshakeResponsePacket'.+--+-- @since 1.4.0+sslRequestPacket :: ExtendedCapabilities -> Word8 -> Packet+sslRequestPacket negotiated charset =+    putToPacket 1 (putSSLRequestExtended negotiated (sslRequestFor negotiated charset))++mkAuth :: ByteString -> ByteString -> ByteString -> Word8 -> Greeting -> Auth+mkAuth = mkAuthFor noExtendedCapabilities++-- | 'mkAuth' for a connection using these extended capabilities.+--+-- @since 1.4.0+mkAuthFor :: ExtendedCapabilities -> ByteString -> ByteString -> ByteString -> Word8 -> Greeting -> Auth+mkAuthFor negotiated db user pass charset greet =+    let salt = greetingSalt1 greet `B.append` greetingSalt2 greet+        plugin = greetingAuthPlugin greet+        scambleBuf = scrambleForPlugin plugin salt pass+    in Auth (clientCapFor negotiated) clientMaxPacketSize charset user scambleBuf db plugin++-- | Dispatch scramble based on the authentication plugin name.+scrambleForPlugin :: ByteString -> ByteString -> ByteString -> ByteString+scrambleForPlugin plugin salt pass+    | plugin == "caching_sha2_password" = scrambleSHA256 salt pass+    | otherwise                         = scrambleSHA1 salt pass++-- | The reply to the salt of an AuthSwitchRequest for this plugin.+--+-- Decision: client_ed25519 is dispatched here rather than in+-- 'scrambleForPlugin', whose exported type has no room for the signer's+-- failure. Nothing is lost: MariaDB always greets with mysql_native_password+-- and asks for client_ed25519 only through an AuthSwitchRequest.+switchScramble :: ByteString -> ByteString -> ByteString -> Either CryptoError ByteString+switchScramble plugin salt pass = if+    | plugin == ed25519Plugin -> scrambleEd25519 salt pass+    | otherwise               -> Right (scrambleForPlugin plugin salt pass)++-- | The client plugin a MariaDB account @IDENTIFIED VIA ed25519@ switches to.+ed25519Plugin :: ByteString+ed25519Plugin = "client_ed25519"++-- | The nonce MariaDB's ed25519 plugin sends; libmariadb refuses any other+-- length.+ed25519NonceLength :: Int+ed25519NonceLength = 32++-- | The @client_ed25519@ reply to a nonce: an Ed25519 signature of the nonce,+-- valid for the key 'ed25519PublicKey' derives from the password. An empty+-- password is signed like any other, as libmariadb does. The 'Left' cannot+-- occur: crypton decodes a scalar from up to 64 bytes, and every scalar here+-- comes from a SHA-512 digest.+--+-- @since 1.5.0+scrambleEd25519 :: ByteString -> ByteString -> Either CryptoError ByteString+scrambleEd25519 nonce pass = eitherCryptoError $ do+    let digest = sha512 pass+    secret <- secretScalar digest+    ephemeral <- Ed.scalarDecodeLong (sha512 (B.drop 32 digest <> nonce))+    let commitment = Ed.pointEncode (Ed.toPoint ephemeral) :: ByteString+    challenge <- Ed.scalarDecodeLong (sha512 (B.concat [commitment, publicKey secret, nonce]))+    pure (commitment <> Ed.scalarEncode (Ed.scalarAdd ephemeral (Ed.scalarMul challenge secret)))++-- | The 32-byte public key MariaDB stores for an ed25519 account with this+-- password; @ed25519_password@ and @mysql.global_priv@ show it base64 encoded+-- without the padding. The 'Left' does not occur, as for 'scrambleEd25519'.+--+-- @since 1.5.0+ed25519PublicKey :: ByteString -> Either CryptoError ByteString+ed25519PublicKey pass = eitherCryptoError (publicKey <$> secretScalar (sha512 pass))++-- | The secret scalar of a password's SHA-512 digest. MariaDB's plugin+-- departs from Ed25519 only here: Ed25519 hashes a 32-byte seed, MariaDB the+-- whole password.+secretScalar :: ByteString -> CryptoFailable Ed.Scalar+secretScalar digest = Ed.scalarDecodeLong (B.pack (zipWith clampByte [0 ..] (B.unpack (B.take 32 digest))))++-- | Ed25519's clamping of the secret scalar's bytes, as MariaDB's ref10+-- @crypto_sign_keypair@ does it.+clampByte :: Int -> Word8 -> Word8+clampByte index byte = if+    | index == 0  -> byte .&. 248+    | index == 31 -> (byte .&. 63) .|. 64+    | otherwise   -> byte++publicKey :: Ed.Scalar -> ByteString+publicKey = Ed.pointEncode . Ed.toPoint++sha512 :: ByteString -> ByteString+sha512 = BA.convert . (Crypto.hash :: ByteString -> Crypto.Digest Crypto.SHA512)++-- | SHA1-based scramble for @mysql_native_password@.+scrambleSHA1 :: ByteString -> ByteString -> ByteString+scrambleSHA1 salt pass+    | B.null pass = B.empty+    | otherwise   = B.pack (B.zipWith xor sha1pass withSalt)+    where sha1pass = sha1 pass+          withSalt = sha1 (salt `B.append` sha1 sha1pass)+          sha1 :: ByteString -> ByteString+          sha1 = BA.convert . (Crypto.hash :: ByteString -> Crypto.Digest Crypto.SHA1)++-- | SHA256-based scramble for @caching_sha2_password@.+-- XOR(SHA256(password), SHA256(SHA256(SHA256(password)) + nonce))+scrambleSHA256 :: ByteString -> ByteString -> ByteString+scrambleSHA256 salt pass+    | B.null pass = B.empty+    | otherwise   = B.pack (B.zipWith xor sha256pass withSalt)+    where sha256pass = sha256 pass+          withSalt   = sha256 (sha256 sha256pass `B.append` salt)+          sha256 :: ByteString -> ByteString+          sha256 = BA.convert . (Crypto.hash :: ByteString -> Crypto.Digest Crypto.SHA256)++-- | Handle multi-step authentication after sending the initial auth response.+--+-- This handles OK, ERR, AuthMoreData (0x01), and AuthSwitchRequest (0xFE).+-- The @fullAuth@ callback is invoked when the server requests full authentication+-- (e.g., cleartext password over TLS).+completeAuth :: InputStream Packet       -- ^ packet input stream+             -> (Packet -> IO ())        -- ^ packet writer+             -> ByteString               -- ^ password+             -> Packet                   -- ^ the first response packet from server+             -> (Word8 -> ByteString -> (Packet -> IO ()) -> InputStream Packet -> IO ())+                                         -- ^ full auth callback (seqN, password, writer, input)+             -> IO ()+completeAuth is writePacket pass p fullAuth+    | isOK p  = return ()+    | isERR p = decodeFromPacket p >>= throwIO . ERRException+    | isAuthMoreData p = do+        let body = L.toStrict (pBody p)+        case B.index body 1 of+            0x03 -> do  -- fast auth success, read the final OK+                ok <- readPacket is+                if isOK ok+                    then return ()+                    else decodeFromPacket ok >>= throwIO . ERRException+            0x04 -> do  -- full auth required+                fullAuth (pSeqN p + 1) pass writePacket is+            _    -> throwIO (UnexpectedPacket p)+    | isAuthSwitch p = do+        -- Parse AuthSwitchRequest: 0xFE, plugin name (NUL), salt+        let body = L.toStrict (pBody p)+            rest = B.drop 1 body  -- skip 0xFE+            (newPlugin, rest') = B.break (== 0) rest+            newSalt' = switchSalt newPlugin (B.drop 1 rest')+            seqN = pSeqN p + 1+        when (newPlugin == ed25519Plugin && B.length newSalt' /= ed25519NonceLength)+            (throwIO (UnexpectedPacket p))+        scrambled <- either (throwIO . AuthException . ("signing the client_ed25519 nonce failed: " ++) . show)+            pure (switchScramble newPlugin newSalt' pass)+        let responseBody = L.fromStrict scrambled+            responsePacket = Packet (fromIntegral (B.length scrambled)) seqN responseBody+        writePacket responsePacket+        q <- readPacket is+        completeAuth is writePacket pass q fullAuth+    | otherwise = throwIO (UnexpectedPacket p)++-- | The salt in the plugin data of an AuthSwitchRequest. The native and+-- SHA-256 scrambles may end in a NUL that is no part of the salt, but+-- MariaDB's ed25519 nonce is random bytes, so a last byte of 0 belongs to it.+switchSalt :: ByteString -> ByteString -> ByteString+switchSalt plugin pluginData = if+    | plugin == ed25519Plugin -> pluginData+    | otherwise -> case B.unsnoc pluginData of+        Just (salt, lastByte) -> if lastByte == 0 then salt else pluginData+        Nothing               -> pluginData++-- | Full auth handler for plain TCP connections using RSA encryption.+--+-- The nonce is captured via partial application at the call site:+-- @completeAuth is writePacket pass q (plainFullAuth salt)@+plainFullAuth :: ByteString -> Word8 -> ByteString -> (Packet -> IO ()) -> InputStream Packet -> IO ()+plainFullAuth nonce seqN pass writePacket is = do+    -- Request server's RSA public key+    writePacket (putToPacket seqN (Binary.putWord8 0x02))+    keyPkt <- readPacket is+    -- Key packet body: 0x01 <PEM bytes>+    let pemKey = L.toStrict (L.drop 1 (pBody keyPkt))+    doRSAAuth writePacket (pSeqN keyPkt + 1) pass nonce pemKey+    q <- readPacket is+    if isOK q then pure ()+              else decodeFromPacket q >>= throwIO . ERRException++-- | Parse a PEM-encoded SubjectPublicKeyInfo RSA public key (PKCS#8 / RFC 5480).+parseRSAPublicKey :: ByteString -> Either String RSA.PublicKey+parseRSAPublicKey pemBytes =+    either (Left . show) Right (PEM.pemParseBS pemBytes)+    >>= firstPEM+    >>= decodeDER+    >>= spkiBitString+    >>= decodeDER+    >>= rsaKey+  where+    firstPEM []    = Left "empty PEM"+    firstPEM (x:_) = Right (PEM.pemContent x)++    decodeDER bs = either (Left . show) Right (ASN1E.decodeASN1' ASN1B.DER bs)++    spkiBitString (ASN1.Start ASN1.Sequence+                   : ASN1.Start ASN1.Sequence+                   : ASN1.OID _+                   : ASN1.Null+                   : ASN1.End ASN1.Sequence+                   : ASN1.BitString bs+                   : ASN1.End ASN1.Sequence+                   : [])+        = Right (ASN1BA.bitArrayGetData bs)+    spkiBitString xs = Left ("unexpected SPKI ASN1: " ++ show xs)++    rsaKey (ASN1.Start ASN1.Sequence+            : ASN1.IntVal n+            : ASN1.IntVal e+            : ASN1.End ASN1.Sequence+            : [])+        = Right RSA.PublicKey+            { RSA.public_size = B.length (Serialize.i2osp n :: ByteString)+            , RSA.public_n    = n+            , RSA.public_e    = e+            }+    rsaKey xs = Left ("unexpected RSA key ASN1: " ++ show xs)++-- | XOR @(password ++ NUL)@ with a cyclically-extended nonce.+xorPasswordNonce :: ByteString -> ByteString -> ByteString+xorPasswordNonce pass nonce =+    let msg      = pass `B.snoc` 0x00+        nonceLen = B.length nonce+        cycled   = B.pack [nonce `B.index` (i `mod` nonceLen) | i <- [0 .. B.length msg - 1]]+    in B.pack (B.zipWith xor msg cycled)++-- | Encrypt password with the server's RSA public key and send it.+doRSAAuth :: (Packet -> IO ()) -> Word8 -> ByteString -> ByteString -> ByteString -> IO ()+doRSAAuth writePacket seqN pass nonce pemKey = do+    pubKey <- either (\e -> throwIO (userError ("RSA key parse: " ++ e))) pure+                     (parseRSAPublicKey pemKey)+    result <- OAEP.encrypt (OAEP.defaultOAEPParams SHA1) pubKey+                           (xorPasswordNonce pass nonce)+    ct <- either (\e -> throwIO (userError ("RSA encrypt: " ++ show e))) pure result+    writePacket (putToPacket seqN (Binary.putByteString ct))++data AuthException = AuthException String deriving (Typeable, Show)+instance Exception AuthException++-- | A specialized 'decodeInputStream' here for speed+decodeInputStream :: InputStream ByteString -> IO (InputStream Packet)+decodeInputStream is = Stream.makeInputStream $ do+    bs <- Stream.readExactly 4 is+    let len =  fromIntegral (bs `B.unsafeIndex` 0)+           .|. fromIntegral (bs `B.unsafeIndex` 1) `shiftL` 8+           .|. fromIntegral (bs `B.unsafeIndex` 2) `shiftL` 16+        seqN = bs `B.unsafeIndex` 3+    body <- loopRead [] len is+    return . Just $ Packet len seqN body+  where+    loopRead acc 0 _  = return $! L.fromChunks (reverse acc)+    loopRead acc k is' = do+        bs <- Stream.read is'+        case bs of Nothing -> throwIO NetworkException+                   Just bs' -> do let l = fromIntegral (B.length bs')+                                  if l >= k+                                  then do+                                      let (a, rest) = B.splitAt (fromIntegral k) bs'+                                      unless (B.null rest) (Stream.unRead rest is')+                                      return $! L.fromChunks (reverse (a:acc))+                                  else do+                                      let k' = k - l+                                      k' `seq` loopRead (bs':acc) k' is'++-- | Close a MySQL connection.+--+close :: MySQLConn -> IO ()+close (MySQLConn _ _ closeSocket _) = closeSocket++-- | Send a 'COM_PING'.+--+ping :: MySQLConn -> IO OK+ping = flip command COM_PING++-- | Give the connection's session the state of a new one without reconnecting+-- or authenticating again, so a pool can hand the connection to its next user.+-- It sends COM_RESET_CONNECTION, which needs MySQL 5.7.3 or MariaDB 10.2.4; an+-- older server's refusal raises 'ERRException'.+--+-- The session stays the same one (@CONNECTION_ID()@ and the user do not+-- change), and the server+--+--   * rolls back an open transaction, and releases @LOCK TABLES@ locks and+--     @GET_LOCK@ locks;+--   * drops the session's temporary tables;+--   * sets every user variable to @NULL@;+--   * deallocates every prepared statement, so executing one of their+--     'StmtID's afterwards raises 'ERRException' with code 1243+--     (@ER_UNKNOWN_STMT_HANDLER@), while 'closeStmt' on one is harmless;+--   * sets the session variables, such as @sql_mode@, @time_zone@,+--     @autocommit@ and the transaction isolation level, to their global+--     values, and @LAST_INSERT_ID()@ to 0.+--+-- The current database stays, also one chosen with @USE@ after connecting.+--+-- Two things differ from a new connection, measured on MySQL 8.0.45 and+-- MariaDB 11.4.9. The session's @sql_mode@ loses the @IGNORE_SPACE@ that the+-- handshake adds, so a query with a space after a function name, as in+-- @COUNT (*)@, becomes a syntax error. And MariaDB returns to the character set+-- and collation of 'ciCharset', where MySQL takes the server's global+-- @character_set_client@ and @collation_connection@ (on 8.0 @utf8mb4@ with+-- @utf8mb4_0900_ai_ci@, whichever 'ciCharset' the connection was made with);+-- send @SET NAMES@ after the reset to keep 'ciCharset' on MySQL.+--+-- Like other commands it refuses with 'UnconsumedResultSet' while a result+-- set's rows are unread, and with 'AbandonedReply' after an interrupted reply,+-- whose unread rest would be taken for the reset's reply: such a connection+-- has to be closed, not reset.+--+-- @since 1.5.0+resetConnection :: MySQLConn -> IO ()+resetConnection conn@(MySQLConn is os _ _) = runCommand conn $ do+    writeResetConnection os+    void (waitCommandReply is)++--------------------------------------------------------------------------------+-- helpers++-- | Send a 'Command' which don't return a resultSet.+--+command :: MySQLConn -> Command -> IO OK+command conn@(MySQLConn is os _ _) cmd = runCommand conn $ do+    writeCommand cmd os+    waitCommandReply is+{-# INLINE command #-}++waitCommandReply :: InputStream Packet -> IO OK+waitCommandReply is = do+    p <- readPacket is+    if  | isERR p -> decodeFromPacket p >>= throwIO . ERRException+        | isOK  p -> decodeFromPacket p+        | otherwise -> throwIO (UnexpectedPacket p)+{-# INLINE waitCommandReply #-}++waitCommandReplys :: InputStream Packet -> IO [OK]+waitCommandReplys is = do+    p <- readPacket is+    if  | isERR p -> decodeFromPacket p >>= throwIO . ERRException+        | isOK  p -> do ok <- decodeFromPacket p+                        if isThereMore ok+                        then (ok :) <$> waitCommandReplys is+                        else return [ok]+        | otherwise -> throwIO (UnexpectedPacket p)+{-# INLINE waitCommandReplys #-}++readPacket :: InputStream Packet -> IO Packet+readPacket is = Stream.read is >>= maybe+    (throwIO NetworkException)+    (\ p@(Packet len _ bs) -> if len < 16777215 then return p else go len [bs])+  where+    go len acc = Stream.read is >>= maybe+        (throwIO NetworkException)+        (\ (Packet len' seqN bs) -> do+            let len'' = len + len'+                acc' = bs:acc+            if len' < 16777215+            then return (Packet len'' seqN (L.concat . reverse $ acc'))+            else len'' `seq` go len'' acc'+        )+{-# INLINE readPacket #-}++writeCommand :: Command -> (Packet -> IO ()) -> IO ()+writeCommand a = writePayload (Binary.runPut (putCommand a))+{-# INLINE writeCommand #-}++-- Decision: COM_RESET_CONNECTION (0x1F) is written here instead of as a+-- 'Command' constructor, because 'Command' is exported and a new constructor+-- would break exhaustive matches on it in code using this library.+writeResetConnection :: (Packet -> IO ()) -> IO ()+writeResetConnection = writePayload (Binary.runPut (Binary.putWord8 0x1F))++-- | Send one command's payload, split into packets as the protocol requires+-- for payloads of 16 MiB and more.+--+-- @since 1.4.0+writePayload :: L.ByteString -> (Packet -> IO ()) -> IO ()+writePayload bs = writePayloadFrom 0 (L.length bs) bs+{-# INLINE writePayload #-}++writePayloadFrom :: Word8 -> Int64 -> L.ByteString -> (Packet -> IO ()) -> IO ()+writePayloadFrom seqN len bs writePacket =+    if len < 16777215+    then writePacket (Packet len seqN bs)+    else do+        let (bs', rest) = L.splitAt 16777215 bs+        writePacket (Packet 16777215 seqN bs')+        writePayloadFrom (seqN + 1) (len - 16777215) rest writePacket++-- | Throws unless a command may be sent, see 'ReplyState'.+guardUnconsumed :: MySQLConn -> IO ()+guardUnconsumed (MySQLConn _ _ _ state) = do+    current <- readIORef state+    case current of+        RepliesRead -> pure ()+        RowsUnread -> throwIO UnconsumedResultSet+        ReplyAbandoned -> throwIO AbandonedReply+{-# INLINE guardUnconsumed #-}++-- | Send a command and read its reply, the @exchange@, leaving the connection+-- at 'ReplyAbandoned' if an exception stops it part-way. An exchange that hands+-- out a row stream sets 'RowsUnread' itself. See Note [Abandoned replies].+runCommand :: MySQLConn -> IO a -> IO a+runCommand conn@(MySQLConn _ _ _ state) exchange = mask $ \restore -> do+    guardUnconsumed conn+    writeIORef state ReplyAbandoned+    result <- restore exchange `catch` settleFailedExchange state+    settled <- readIORef state+    when (settled == ReplyAbandoned) (writeIORef state RepliesRead)+    pure result++settleFailedExchange :: IORef ReplyState -> SomeException -> IO a+settleFailedExchange state failure = do+    when (endsReply failure) (writeIORef state RepliesRead)+    throwIO failure++-- | Raised only once the reply it came in was read to the end.+endsReply :: SomeException -> Bool+endsReply failure =+    isJust (fromException failure :: Maybe ERRException)+    || isJust (fromException failure :: Maybe ExtraResultSets)++writeIORef' :: IORef a -> a -> IO ()+writeIORef' ref x = x `seq` writeIORef ref x+{-# INLINE writeIORef' #-}++--------------------------------------------------------------------------------+-- Exceptions++data NetworkException = NetworkException deriving (Typeable, Show)+instance Exception NetworkException++data UnconsumedResultSet = UnconsumedResultSet deriving (Typeable, Show)+instance Exception UnconsumedResultSet++-- | Reading the reply to an earlier command on this connection stopped part-way,+-- for example on a 'System.Timeout.timeout'. The rest of that reply is still+-- unread and would be taken for the reply to the next command, so the+-- connection refuses commands: close it and connect again.+--+-- @since 1.4.0+data AbandonedReply = AbandonedReply deriving (Typeable, Show)+instance Exception AbandonedReply where+    displayException AbandonedReply =+        "mysql-haskell: reading the reply to an earlier command on this connection stopped "+        ++ "part-way (for example on a timeout), so the rest of it is still unread and "+        ++ "would be taken for this command's reply. Close the connection and connect again."++data ERRException = ERRException ERR deriving (Typeable, Show)+instance Exception ERRException++data UnexpectedPacket = UnexpectedPacket Packet deriving (Typeable, Show)+instance Exception UnexpectedPacket++-- | A statement produced a result set the function running it could not+-- return: @execute@ and @executeStmt@ return none, the query functions only the+-- first. It was read and discarded, so the connection is still usable.+--+-- @since 1.3.2+data ExtraResultSets = ExtraResultSets deriving (Typeable, Show)+instance Exception ExtraResultSets where+    displayException ExtraResultSets =+        "mysql-haskell: the statement produced a result set this function could not "+        ++ "return (execute_ and executeStmt return none, query_ and queryStmt only the "+        ++ "first). It was read and discarded, so the connection is still usable. Run "+        ++ "statements that return rows through a query function, one result set per call."+
+ src/Database/MySQL/Decoder.hs view
@@ -0,0 +1,848 @@+{-# LANGUAGE MagicHash     #-}+{-# LANGUAGE UnboxedSums   #-}+{-# LANGUAGE UnboxedTuples #-}++{-|+Module      : Database.MySQL.Decoder+Description : Decode rows straight into Haskell values, without 'MySQLValue'+Copyright   : (c) Jappie Klooster, 2026+License     : BSD3+Maintainer  : hi@jappie.me+Stability   : experimental+Portability : PORTABLE++Decoders that turn a row's fields straight into Haskell values, for+'Database.MySQL.Base.queryRows' and 'Database.MySQL.Base.queryStmtRows':++@+import qualified Database.MySQL.Decoder as Decode++data Employee = Employee { number :: Int32, born :: Day, name :: Text }++employee :: Decode.RowDecoder Employee+employee = Employee+    \<$\> Decode.field Decode.int32+    \<*\> Decode.field Decode.day+    \<*\> Decode.field Decode.text++(columns, employees) <- queryRows_ employee conn "SELECT emp_no, birth_date, first_name FROM employees"+@++A 'FieldDecoder' is checked against its column's definition once per result+set; a column it does not fit raises a 'ColumnMismatch' before any row is+read. A decoder for a Haskell type accepts exactly the columns whose+'MySQLValue' constructor "Database.MySQL.Field" decodes into that type, so+the two never disagree. Values are evaluated as they are decoded, so a field+that does not decode raises a 'FieldError' with its column while the row is+read.++Libraries that decode by column number, such as a persistent backend, can use+'prepareFieldParser' and 'runFieldParser' on the 'RawRow's of+'Database.MySQL.Base.queryRawRows_' and 'Database.MySQL.Base.queryStmtRawRows'+instead of a 'RowDecoder'. See @docs/direct-row-decoding.md@.+-}+module Database.MySQL.Decoder+  ( -- * Decoding a column+    FieldDecoder+  , int8, word8, int16, word16, int32, word32, int64, word64+  , float, double, scientific+  , text, bytes+  , day, localTime, timeOfDay+  , bool+  , mysqlValue+  , nullable+    -- * Decoding a row+  , RowDecoder+  , field+    -- * Errors+  , ColumnMismatch(..)+  , FieldError(..)+  , FieldErrorKind(..)+    -- * Running decoders on raw rows+  , RowProtocol+  , FieldParser+  , prepareFieldParser+  , runFieldParser+  , PreparedRow+  , prepareRowDecoder+  , runPreparedRow+    -- * Prepared statements' rows as 'MySQLValue's+  , BinaryValueColumns+  , binaryValueColumns+  , decodeBinaryValues+  , module Database.MySQL.Protocol.RawRow+  ) where++import           Control.Exception                  (Exception)+import           Data.Bits                          (unsafeShiftL, (.|.))+import           Data.ByteString                    (ByteString)+import qualified Data.ByteString                    as B+import qualified Data.ByteString.Unsafe             as B+import           Data.Kind                          (Type)+import           Data.Fixed                         (Fixed (MkFixed), Pico)+import           Data.Int                           (Int16, Int32, Int64, Int8)+import           Data.Scientific                    (Scientific)+import           Data.Text                          (Text)+import qualified Data.Text.Encoding                 as T+import           Data.Time.Calendar                 (Day)+import           Data.Time.LocalTime                (LocalTime (..),+                                                     TimeOfDay (..), midnight)+import qualified Data.Vector                        as V+import           Data.Word                          (Word16, Word32, Word64,+                                                     Word8)+import           Database.MySQL.Protocol.ColumnDef+import           Database.MySQL.Protocol.FloatingPoint (readDouble, readFloat)+import           Database.MySQL.Protocol.MySQLValue (ColumnKind (..),+                                                     MySQLValue (..),+                                                     RawDate (..),+                                                     RowErrorKind (..),+                                                     nextColumn,+                                                     TextFieldError (..),+                                                     ValueKind (..),+                                                     calendarDate, columnKind,+                                                     dateTimeValue, dateValue,+                                                     decodeMySQLLatin1,+                                                     decodeTextBit,+                                                     decodeTextValue,+                                                     lexDateTimeParts,+                                                     textDateParts,+                                                     timeStampValue,+                                                     lexSignedFraction,+                                                     lexSignedIntegral,+                                                     lengthEncodedInt,+                                                     lexSignedTime)+-- The internal utilities come in qualified only, so that the @module@+-- re-export in the export list does not publish them.+import           Database.MySQL.Protocol.RawRow      hiding (binaryNullMapLength,+                                                             isNullInMap)+import qualified Database.MySQL.Protocol.RawRow      as RawRow+import           GHC.Exts                           (word16ToInt16#,+                                                     word32ToInt32#,+                                                     word64ToInt64#,+                                                     word8ToInt8#)+import           GHC.Float                          (castWord32ToFloat,+                                                     castWord64ToDouble)+import           GHC.Int                            (Int16 (I16#), Int32 (I32#),+                                                     Int64 (I64#), Int8 (I8#))+import           GHC.Word                           (Word16 (W16#),+                                                     Word32 (W32#),+                                                     Word64 (W64#), Word8 (W8#))+import qualified Unwitch.Convert.Word32             as Word32+import qualified Unwitch.Convert.Word8              as Word8++-- | How the non-NULL bytes of one field become a value. An unboxed sum, so+-- that no 'Either' is allocated per field.+type ParseBytes a = ByteString -> (# FieldErrorKind | a #)++-- | Turns the fields of one column into @a@. See the module header.+--+-- @since 1.4.0+data FieldDecoder a = FieldDecoder+    { decoderTypeName :: !Text+      -- ^ the Haskell type, for 'ColumnTypeMismatch'+    , decoderNull     :: !(Maybe a)+      -- ^ the value of a NULL field; 'Nothing' makes a NULL a 'FieldUnexpectedNull'+    , decoderText     :: ColumnKind -> Maybe (ParseBytes a)+      -- ^ the parser for a column's text-protocol fields, if the column fits+    , decoderBinary   :: ColumnKind -> Maybe (ParseBytes a)+      -- ^ the parser for a column's binary-protocol fields, if the column fits+    }++instance Functor FieldDecoder where+    fmap f decoder = FieldDecoder+        { decoderTypeName = decoderTypeName decoder+        , decoderNull     = f <$> decoderNull decoder+        , decoderText     = fmap (mapParse f) . decoderText decoder+        , decoderBinary   = fmap (mapParse f) . decoderBinary decoder+        }++mapParse :: (a -> b) -> ParseBytes a -> ParseBytes b+mapParse f parse = \fieldBytes -> case parse fieldBytes of+    (# kind | #)  -> (# kind | #)+    (# | value #) -> (# | f value #)+{-# INLINE mapParse #-}++-- | NULL becomes 'Nothing'; any other field is decoded as before.+--+-- @since 1.4.0+nullable :: FieldDecoder a -> FieldDecoder (Maybe a)+nullable decoder = (Just <$> decoder)+    { decoderTypeName = "Maybe " <> decoderTypeName decoder+    , decoderNull     = Just Nothing+    }++-- | A decoder for the columns whose fields become one of @kinds@. A column of+-- type NULL fits every decoder: all its fields are NULL, which 'decoderNull'+-- answers.+kindDecoder :: Text -> [ValueKind] -> ParseBytes a -> ParseBytes a -> FieldDecoder a+kindDecoder typeName kinds textParse binaryParse = FieldDecoder+    { decoderTypeName = typeName+    , decoderNull     = Nothing+    , decoderText     = whenKindIn kinds textParse+    , decoderBinary   = whenKindIn kinds binaryParse+    }++whenKindIn :: [ValueKind] -> ParseBytes a -> ColumnKind -> Maybe (ParseBytes a)+whenKindIn kinds parse column = case column of+    NullColumn         -> Just parse+    ValueColumn _ kind -> if kind `elem` kinds then Just parse else Nothing++-- | TINYINT.+int8 :: FieldDecoder Int8+int8 = kindDecoder "Int8" [KindInt8] (lexedField lexSignedIntegral)+    (fixedField 1 (int8Bits . B.unsafeHead))++-- | TINYINT UNSIGNED.+word8 :: FieldDecoder Word8+word8 = kindDecoder "Word8" [KindInt8U] (lexedField lexSignedIntegral)+    (fixedField 1 B.unsafeHead)++-- | SMALLINT.+int16 :: FieldDecoder Int16+int16 = kindDecoder "Int16" [KindInt16] (lexedField lexSignedIntegral)+    (fixedField 2 (int16Bits . littleEndian16))++-- | SMALLINT UNSIGNED.+word16 :: FieldDecoder Word16+word16 = kindDecoder "Word16" [KindInt16U] (lexedField lexSignedIntegral)+    (fixedField 2 littleEndian16)++-- | INT and MEDIUMINT.+int32 :: FieldDecoder Int32+int32 = kindDecoder "Int32" [KindInt32] (lexedField lexSignedIntegral)+    (fixedField 4 (int32Bits . littleEndian32))++-- | INT UNSIGNED and MEDIUMINT UNSIGNED.+word32 :: FieldDecoder Word32+word32 = kindDecoder "Word32" [KindInt32U] (lexedField lexSignedIntegral)+    (fixedField 4 littleEndian32)++-- | BIGINT.+int64 :: FieldDecoder Int64+int64 = kindDecoder "Int64" [KindInt64] (lexedField lexSignedIntegral)+    (fixedField 8 (int64Bits . littleEndian64))++-- | BIGINT UNSIGNED.+word64 :: FieldDecoder Word64+word64 = kindDecoder "Word64" [KindInt64U] (lexedField lexSignedIntegral)+    (fixedField 8 littleEndian64)++-- | FLOAT.+float :: FieldDecoder Float+float = kindDecoder "Float" [KindFloat] (lexedField readFloat)+    (fixedField 4 (castWord32ToFloat . littleEndian32))++-- | DOUBLE.+double :: FieldDecoder Double+double = kindDecoder "Double" [KindDouble] (lexedField readDouble)+    (fixedField 8 (castWord64ToDouble . littleEndian64))++-- | DECIMAL; the binary protocol sends it as text too.+scientific :: FieldDecoder Scientific+scientific = kindDecoder "Scientific" [KindDecimal] (lexedField lexSignedFraction)+    (lexedField lexSignedFraction)++-- | The string columns in a character set other than binary. latin1 is read as+-- MySQL means it, as Windows-1252, and any other as UTF-8, where invalid UTF-8+-- is a 'FieldInvalidUtf8'.+text :: FieldDecoder Text+text = FieldDecoder+    { decoderTypeName = "Text"+    , decoderNull     = Nothing+    , decoderText     = stringColumnParse+    , decoderBinary   = stringColumnParse+    }++stringColumnParse :: ColumnKind -> Maybe (ParseBytes Text)+stringColumnParse column = case column of+    NullColumn -> Just utf8Field+    ValueColumn _ kind -> case kind of+        KindText _      -> Just utf8Field+        KindLatin1Text  -> Just latin1Field+        KindDecimal     -> Nothing+        KindInt8U       -> Nothing+        KindInt8        -> Nothing+        KindInt16U      -> Nothing+        KindInt16       -> Nothing+        KindInt32U      -> Nothing+        KindInt32       -> Nothing+        KindInt64U      -> Nothing+        KindInt64       -> Nothing+        KindFloat       -> Nothing+        KindDouble      -> Nothing+        KindYear        -> Nothing+        KindTimeStamp   -> Nothing+        KindDateTime    -> Nothing+        KindDate        -> Nothing+        KindTime        -> Nothing+        KindGeometry    -> Nothing+        KindBytes       -> Nothing+        KindBit         -> Nothing+        KindUnsupported -> Nothing++-- | The string columns in the binary character set: BINARY, VARBINARY, BLOB.+bytes :: FieldDecoder ByteString+bytes = kindDecoder "ByteString" [KindBytes] bytesField bytesField++-- | DATE. A date a 'Day' cannot hold, such as @0000-00-00@, is a+-- 'FieldUnrepresentableDate'.+day :: FieldDecoder Day+day = kindDecoder "Day" [KindDate] (calendarDay (lexedField textDateParts))+    (calendarDay binaryDateParts)++-- | DATETIME and TIMESTAMP. A date a 'Day' cannot hold is a+-- 'FieldUnrepresentableDate'.+localTime :: FieldDecoder LocalTime+localTime = kindDecoder "LocalTime" [KindDateTime, KindTimeStamp]+    (calendarLocalTime (lexedField lexDateTimeParts)) (calendarLocalTime binaryDateTimeParts)++-- | TIME, when not negative; a negative one is a 'FieldNegativeTime'. The+-- hours may exceed 24, as TIME spans up to 838 hours.+timeOfDay :: FieldDecoder TimeOfDay+timeOfDay = kindDecoder "TimeOfDay" [KindTime]+    (nonNegativeTime (lexedField lexSignedTime)) (nonNegativeTime binarySignedTime)++-- | TINYINT and TINYINT UNSIGNED, as MySQL stores BOOL: anything but 0 is 'True'.+bool :: FieldDecoder Bool+bool = FieldDecoder+    { decoderTypeName = "Bool"+    , decoderNull     = Nothing+    , decoderText     = boolText+    , decoderBinary   = whenKindIn [KindInt8, KindInt8U] (fixedField 1 ((/= 0) . B.unsafeHead))+    }++-- | The signed and unsigned text of a TINYINT overflow differently, so each+-- kind keeps its own type, as 'Database.MySQL.Field.decodeBool' does.+boolText :: ColumnKind -> Maybe (ParseBytes Bool)+boolText column = case column of+    NullColumn -> Just (lexedField lexNonZeroInt8)+    ValueColumn _ kind ->+        if  | kind == KindInt8  -> Just (lexedField lexNonZeroInt8)+            | kind == KindInt8U -> Just (lexedField lexNonZeroWord8)+            | otherwise         -> Nothing++lexNonZeroInt8 :: ByteString -> Maybe Bool+lexNonZeroInt8 fieldBytes = (/= (0 :: Int8)) <$> lexSignedIntegral fieldBytes++lexNonZeroWord8 :: ByteString -> Maybe Bool+lexNonZeroWord8 fieldBytes = (/= (0 :: Word8)) <$> lexSignedIntegral fieldBytes++-- | Every column, as 'Database.MySQL.Base.query_' decodes it; NULL is+-- 'MySQLNull'. The values are as lazy as 'query_' leaves them.+mysqlValue :: FieldDecoder MySQLValue+mysqlValue = FieldDecoder+    { decoderTypeName = "MySQLValue"+    , decoderNull     = Just MySQLNull+    , decoderText     = Just . textMySQLValue+    , decoderBinary   = Just . binaryMySQLValue+    }++textMySQLValue :: ColumnKind -> ParseBytes MySQLValue+textMySQLValue column fieldBytes = case column of+    NullColumn -> (# | MySQLNull #)+    -- 'decodeTextValue' leaves a string's UTF-8 to be checked when the value is+    -- used; a decoder checks while it reads the row, see the module header.+    ValueColumn _ (KindText _) -> case utf8Field fieldBytes of+        (# fieldError | #) -> (# fieldError | #)+        (# | decoded #)    -> (# | MySQLText decoded #)+    ValueColumn fieldType kind -> case decodeTextValue fieldType kind fieldBytes of+        Left fieldError -> (# textFieldErrorKind fieldError | #)+        Right value     -> (# | value #)++-- | 'Database.MySQL.Protocol.MySQLValue.getBinaryField' on a field's bytes.+binaryMySQLValue :: ColumnKind -> ParseBytes MySQLValue+binaryMySQLValue column fieldBytes = case column of+    NullColumn -> (# | MySQLNull #)+    ValueColumn fieldType kind -> case kind of+        KindDecimal     -> mapParse MySQLDecimal (lexedField lexSignedFraction) fieldBytes+        KindInt8U       -> mapParse MySQLInt8U (fixedField 1 B.unsafeHead) fieldBytes+        KindInt8        -> mapParse MySQLInt8 (fixedField 1 (int8Bits . B.unsafeHead)) fieldBytes+        KindInt16U      -> mapParse MySQLInt16U (fixedField 2 littleEndian16) fieldBytes+        KindInt16       -> mapParse MySQLInt16 (fixedField 2 (int16Bits . littleEndian16)) fieldBytes+        KindInt32U      -> mapParse MySQLInt32U (fixedField 4 littleEndian32) fieldBytes+        KindInt32       -> mapParse MySQLInt32 (fixedField 4 (int32Bits . littleEndian32)) fieldBytes+        KindInt64U      -> mapParse MySQLInt64U (fixedField 8 littleEndian64) fieldBytes+        KindInt64       -> mapParse MySQLInt64 (fixedField 8 (int64Bits . littleEndian64)) fieldBytes+        KindFloat       -> mapParse MySQLFloat (fixedField 4 (castWord32ToFloat . littleEndian32)) fieldBytes+        KindDouble      -> mapParse MySQLDouble (fixedField 8 (castWord64ToDouble . littleEndian64)) fieldBytes+        KindYear        -> mapParse MySQLYear (fixedField 2 littleEndian16) fieldBytes+        KindTimeStamp   -> mapParse (uncurry timeStampValue) binaryDateTimeParts fieldBytes+        KindDateTime    -> mapParse (uncurry dateTimeValue) binaryDateTimeParts fieldBytes+        KindDate        -> mapParse dateValue binaryDateParts fieldBytes+        KindTime        -> mapParse (uncurry MySQLTime) binarySignedTime fieldBytes+        KindGeometry    -> (# | MySQLGeometry fieldBytes #)+        -- Both protocols send a string as its bytes, so 'decodeTextValue' decodes it.+        KindText _      -> textMySQLValue column fieldBytes+        KindLatin1Text  -> textMySQLValue column fieldBytes+        KindBytes       -> (# | MySQLBytes fieldBytes #)+        KindBit         -> case decodeTextBit fieldBytes of+            Left fieldError -> (# textFieldErrorKind fieldError | #)+            Right value     -> (# | value #)+        KindUnsupported -> (# FieldUnsupportedType fieldType | #)++textFieldErrorKind :: TextFieldError -> FieldErrorKind+textFieldErrorKind fieldError = case fieldError of+    TextFieldUnparsable _ fieldBytes     -> FieldUnparsable fieldBytes+    TextFieldBitTooWide width            -> FieldBitTooWide width+    TextFieldUnsupportedType fieldType   -> FieldUnsupportedType fieldType+    TextFieldInvalidUtf8 _ fieldBytes    -> FieldInvalidUtf8 fieldBytes++-- | A text field the lexer reads, evaluated.+lexedField :: (ByteString -> Maybe a) -> ParseBytes a+lexedField lexer = \fieldBytes -> case lexer fieldBytes of+    Just !value -> (# | value #)+    Nothing     -> (# FieldUnparsable fieldBytes | #)+{-# INLINE lexedField #-}++utf8Field :: ParseBytes Text+utf8Field fieldBytes = case T.decodeUtf8' fieldBytes of+    Right !decoded -> (# | decoded #)+    Left _         -> (# FieldInvalidUtf8 fieldBytes | #)++latin1Field :: ParseBytes Text+latin1Field fieldBytes = let !decoded = decodeMySQLLatin1 fieldBytes in (# | decoded #)++bytesField :: ParseBytes ByteString+bytesField fieldBytes = (# | fieldBytes #)++-- | A binary field of exactly @width@ bytes, evaluated.+fixedField :: Int -> (ByteString -> a) -> ParseBytes a+fixedField width decode = \fieldBytes ->+    if B.length fieldBytes == width+    then let !value = decode fieldBytes in (# | value #)+    else (# FieldUnparsable fieldBytes | #)+{-# INLINE fixedField #-}++-- | The binary protocol sends signed integers in two's complement, so the+-- signed value has the same bits as the unsigned one read from the wire.+int8Bits :: Word8 -> Int8+int8Bits (W8# bits) = I8# (word8ToInt8# bits)++int16Bits :: Word16 -> Int16+int16Bits (W16# bits) = I16# (word16ToInt16# bits)++int32Bits :: Word32 -> Int32+int32Bits (W32# bits) = I32# (word32ToInt32# bits)++int64Bits :: Word64 -> Int64+int64Bits (W64# bits) = I64# (word64ToInt64# bits)++littleEndian16 :: ByteString -> Word16+littleEndian16 = B.foldr' prependByte16 0++littleEndian32 :: ByteString -> Word32+littleEndian32 = B.foldr' prependByte32 0++littleEndian64 :: ByteString -> Word64+littleEndian64 = B.foldr' prependByte64 0++prependByte16 :: Word8 -> Word16 -> Word16+prependByte16 byte higherBytes = unsafeShiftL higherBytes 8 .|. Word8.toWord16 byte++prependByte32 :: Word8 -> Word32 -> Word32+prependByte32 byte higherBytes = unsafeShiftL higherBytes 8 .|. Word8.toWord32 byte++prependByte64 :: Word8 -> Word64 -> Word64+prependByte64 byte higherBytes = unsafeShiftL higherBytes 8 .|. Word8.toWord64 byte++-- | A binary DATE: no bytes for the zero date, or a 2-byte year, month and day.+binaryDateParts :: ParseBytes RawDate+binaryDateParts fieldBytes =+    if  | B.null fieldBytes        -> (# | RawDate 0 0 0 #)+        | B.length fieldBytes == 4 -> let !date = binaryDate fieldBytes in (# | date #)+        | otherwise                -> (# FieldUnparsable fieldBytes | #)++binaryDate :: ByteString -> RawDate+binaryDate fieldBytes = RawDate+    (littleEndian16 (B.unsafeTake 2 fieldBytes))+    (B.unsafeIndex fieldBytes 2)+    (B.unsafeIndex fieldBytes 3)++-- | A binary DATETIME or TIMESTAMP: its length says which parts are present,+-- 0 (the zero date), 4 (the date), 7 (and the time) or 11 (and microseconds).+binaryDateTimeParts :: ParseBytes (RawDate, TimeOfDay)+binaryDateTimeParts fieldBytes =+    if  | B.null fieldBytes ->+            (# | (RawDate 0 0 0, midnight) #)+        | B.length fieldBytes == 4 ->+            let !value = (binaryDate fieldBytes, midnight) in (# | value #)+        | B.length fieldBytes == 7 || B.length fieldBytes == 11 ->+            let !value = (binaryDate fieldBytes, binaryClock (B.unsafeDrop 4 fieldBytes))+            in (# | value #)+        | otherwise -> (# FieldUnparsable fieldBytes | #)++calendarDay :: ParseBytes RawDate -> ParseBytes Day+calendarDay parse = \fieldBytes -> case parse fieldBytes of+    (# kind | #) -> (# kind | #)+    (# | raw #) -> case calendarDate raw of+        Right !date -> (# | date #)+        Left unrepresentable -> (# FieldUnrepresentableDate unrepresentable | #)+{-# INLINE calendarDay #-}++calendarLocalTime :: ParseBytes (RawDate, TimeOfDay) -> ParseBytes LocalTime+calendarLocalTime parse = \fieldBytes -> case parse fieldBytes of+    (# kind | #) -> (# kind | #)+    (# | (raw, time) #) -> case calendarDate raw of+        Right date -> let !value = LocalTime date time in (# | value #)+        Left unrepresentable -> (# FieldUnrepresentableDate unrepresentable | #)+{-# INLINE calendarLocalTime #-}++-- | Hours, minutes and seconds, then microseconds when 7 bytes are left.+binaryClock :: ByteString -> TimeOfDay+binaryClock clock = TimeOfDay+    (Word8.toInt (B.unsafeIndex clock 0))+    (Word8.toInt (B.unsafeIndex clock 1))+    (binarySeconds (B.unsafeIndex clock 2) (B.unsafeDrop 3 clock))++-- | Whole seconds plus the 4-byte microseconds if present, exactly: a 'Pico'+-- counts 10^12 per second.+binarySeconds :: Word8 -> ByteString -> Pico+binarySeconds seconds microseconds = MkFixed+    (Word8.toInteger seconds * 1000000000000+        + (if B.length microseconds == 4+           then Word32.toInteger (littleEndian32 microseconds) * 1000000+           else 0))++-- | A binary TIME: no bytes for zero, or a sign, 4-byte days, hours, minutes,+-- seconds and, in the 12-byte form, microseconds. The sign is 1 for negative.+binarySignedTime :: ParseBytes (Word8, TimeOfDay)+binarySignedTime fieldBytes =+    if  | B.null fieldBytes -> (# | (0, midnight) #)+        | B.length fieldBytes == 8 || B.length fieldBytes == 12 ->+            case Word32.toInt (littleEndian32 (B.unsafeTake 4 (B.unsafeDrop 1 fieldBytes))) of+                Nothing -> (# FieldUnparsable fieldBytes | #)+                Just days ->+                    let !value = ( B.unsafeHead fieldBytes+                                 , TimeOfDay (days * 24 + Word8.toInt (B.unsafeIndex fieldBytes 5))+                                             (Word8.toInt (B.unsafeIndex fieldBytes 6))+                                             (binarySeconds (B.unsafeIndex fieldBytes 7)+                                                            (B.unsafeDrop 8 fieldBytes)) )+                    in (# | value #)+        | otherwise -> (# FieldUnparsable fieldBytes | #)++nonNegativeTime :: ParseBytes (Word8, TimeOfDay) -> ParseBytes TimeOfDay+nonNegativeTime parse = \fieldBytes -> case parse fieldBytes of+    (# kind | #) -> (# kind | #)+    (# | (sign, time) #) ->+        if sign == 0 then (# | time #) else (# FieldNegativeTime fieldBytes | #)+{-# INLINE nonNegativeTime #-}++-- | Decodes a row, one 'field' per column, in order. It is checked against the+-- result set's column definitions once, before the first row.+--+-- @since 1.4.0+data RowDecoder a = RowDecoder+    !Int+    -- ^ how many columns it reads+    (forall protocol. RowProtocol protocol+        => V.Vector ColumnDef -> ColumnNumber -> Either ColumnMismatch (FieldSteps protocol a))+    -- ^ fits it to the columns from the given column number on++-- Decision: the instance methods and the step combinators are inlined, so that+-- a decoder written as one expression, such as @Employee \<$\> field a \<*\>+-- field b@, compiles to one walk that applies @Employee@ to all its fields at+-- once. Left to the closures of the run-time composition, every field paid a+-- generic partial application of the constructor (about 1,400 of 9,300+-- instructions per row on the employees benchmark).+instance Functor RowDecoder where+    fmap f (RowDecoder width prepare) =+        RowDecoder width (\columns start -> mapSteps f <$> prepare columns start)+    {-# INLINE fmap #-}++instance Applicative RowDecoder where+    pure value = RowDecoder 0 (\_ _ -> Right (pureSteps value))+    {-# INLINE pure #-}+    RowDecoder functionWidth prepareFunction <*> RowDecoder argumentWidth prepareArgument =+        RowDecoder (functionWidth + argumentWidth) (\columns start ->+            apSteps <$> prepareFunction columns start+                    <*> prepareArgument columns (columnsAfter start functionWidth))+    {-# INLINE (<*>) #-}++-- | Decodes the next column with this decoder.+--+-- @since 1.4.0+field :: FieldDecoder a -> RowDecoder a+field decoder = RowDecoder 1 (\columns column@(ColumnNumber number) -> case columns V.!? number of+    Nothing -> Left (ColumnCountMismatch (number + 1) (V.length columns))+    Just definition ->+        fieldSteps column definition <$> prepareFieldParser decoder column definition)+{-# INLINE field #-}++-- | The column @count@ columns after @column@.+columnsAfter :: ColumnNumber -> Int -> ColumnNumber+columnsAfter (ColumnNumber column) count = ColumnNumber (column + count)++-- | Reads fields from a row's bytes at an offset, and returns the offset after+-- them. A row is walked once, in column order, each field parsed as it is+-- reached, so no field bounds are stored.+newtype FieldSteps (protocol :: Type) a = FieldSteps (ByteString -> Int -> (# FieldError | (# a, Int #) #))++pureSteps :: a -> FieldSteps protocol a+pureSteps value = FieldSteps (\_ offset -> (# | (# value, offset #) #))+{-# INLINE pureSteps #-}++mapSteps :: (a -> b) -> FieldSteps protocol a -> FieldSteps protocol b+mapSteps f (FieldSteps run) = FieldSteps (\row offset -> case run row offset of+    (# fieldError | #)        -> (# fieldError | #)+    (# | (# value, next #) #) -> let !mapped = f value in (# | (# mapped, next #) #))+{-# INLINE mapSteps #-}++-- | Applies as soon as both sides are decoded, so a row decodes to an evaluated+-- value instead of a chain of thunks that each later force has to update.+apSteps :: FieldSteps protocol (a -> b) -> FieldSteps protocol a -> FieldSteps protocol b+apSteps (FieldSteps runFunction) (FieldSteps runArgument) = FieldSteps (\row offset ->+    case runFunction row offset of+        (# fieldError | #) -> (# fieldError | #)+        (# | (# function, afterFunction #) #) -> case runArgument row afterFunction of+            (# fieldError | #) -> (# fieldError | #)+            (# | (# argument, afterArgument #) #) ->+                let !applied = function argument in (# | (# applied, afterArgument #) #))+{-# INLINE apSteps #-}++-- | A 'RowDecoder' fitted to a result set's columns.+--+-- @since 1.4.0+data PreparedRow (protocol :: Type) a = PreparedRow (RowStart protocol) (FieldSteps protocol a)++-- | Where a row's first field starts, or why the row is too short to have one.+newtype RowStart (protocol :: Type) = RowStart (ByteString -> (# FieldError | Int #))++-- | Fits a 'RowDecoder' to a result set's columns. It must read exactly as many+-- columns as there are, each of a type its decoder accepts.+--+-- @since 1.4.0+prepareRowDecoder :: RowProtocol protocol+                  => RowDecoder a -> V.Vector ColumnDef -> Either ColumnMismatch (PreparedRow protocol a)+prepareRowDecoder (RowDecoder width prepare) columns =+    if width /= V.length columns+    then Left (ColumnCountMismatch width (V.length columns))+    else PreparedRow (rowStart width) <$> prepare columns (ColumnNumber 0)++-- | Decodes the body of a row packet of the protocol. Bytes after the last+-- field are ignored, as 'decodeTextRow' ignores them.+--+-- @since 1.4.0+runPreparedRow :: PreparedRow protocol a -> ByteString -> Either FieldError a+runPreparedRow (PreparedRow (RowStart start) (FieldSteps run)) row = case start row of+    (# fieldError | #) -> Left fieldError+    (# | offset #) -> case run row offset of+        (# fieldError | #)     -> Left fieldError+        (# | (# value, _ #) #) -> Right value++-- | 'TextProtocol' or 'BinaryProtocol': how the rows of that protocol lay out+-- their fields, and which of a 'FieldDecoder''s parsers they need.+--+-- @since 1.4.0+class RowProtocol (protocol :: Type) where+    protocolParser :: FieldDecoder a -> ColumnKind -> Maybe (FieldParser protocol a)+    fieldSteps     :: ColumnNumber -> ColumnDef -> FieldParser protocol a -> FieldSteps protocol a+    rowStart       :: Int -> RowStart protocol++instance RowProtocol TextProtocol where+    protocolParser decoder column = FieldParser (decoderNull decoder) <$> decoderText decoder column+    fieldSteps column _ parser = FieldSteps (textFieldStep column parser)+    rowStart _ = RowStart textRowStart++instance RowProtocol BinaryProtocol where+    protocolParser decoder column = FieldParser (decoderNull decoder) <$> decoderBinary decoder column+    fieldSteps column definition parser =+        FieldSteps (binaryFieldStep column (binaryWidth definition) parser)+    rowStart columnCount = RowStart (binaryRowStart columnCount)++textRowStart :: ByteString -> (# FieldError | Int #)+textRowStart _ = (# | 0 #)++-- | A text-protocol field: the NULL marker or a length-encoded value.+textFieldStep :: ColumnNumber -> FieldParser TextProtocol a -> ByteString -> Int -> (# FieldError | (# a, Int #) #)+textFieldStep column parser = \row offset ->+    if  | offset >= B.length row -> (# FieldError column FieldRowEndsEarly | #)+        | B.unsafeIndex row offset == 0xFB -> nullStep column parser (offset + 1)+        | otherwise -> lengthEncodedStep column parser row offset+{-# INLINE textFieldStep #-}++-- | The fields of a binary-protocol row follow the 0x00 header and the NULL map.+binaryRowStart :: Int -> ByteString -> (# FieldError | Int #)+binaryRowStart columnCount row =+    let start = 1 + RawRow.binaryNullMapLength columnCount+    in if start > B.length row then (# FieldError (ColumnNumber 0) FieldRowEndsEarly | #) else (# | start #)++-- | A binary-protocol field: absent when the NULL map marks it, otherwise laid+-- out as its column's 'BinaryWidth' says.+binaryFieldStep :: ColumnNumber -> BinaryWidth -> FieldParser BinaryProtocol a -> ByteString -> Int+                -> (# FieldError | (# a, Int #) #)+binaryFieldStep column width parser = \row offset ->+    if RawRow.isNullInMap row column+    then nullStep column parser offset+    else case width of+        BinaryNoBytes       -> sliceStep column parser row offset 0+        BinaryFixed size    -> sliceStep column parser row offset size+        BinaryLengthEncoded ->+            if offset >= B.length row+            then (# FieldError column FieldRowEndsEarly | #)+            else lengthEncodedStep column parser row offset+{-# INLINE binaryFieldStep #-}++nullStep :: ColumnNumber -> FieldParser protocol a -> Int -> (# FieldError | (# a, Int #) #)+nullStep column parser next = case parserNull parser of+    Just value -> (# | (# value, next #) #)+    Nothing    -> (# FieldError column FieldUnexpectedNull | #)++-- | A length at @offset@, which must be inside the row, then that many bytes.+lengthEncodedStep :: ColumnNumber -> FieldParser protocol a -> ByteString -> Int -> (# FieldError | (# a, Int #) #)+lengthEncodedStep column parser row offset = case lengthEncodedInt row offset of+    (# kind | #) -> (# FieldError column (lengthErrorKind kind) | #)+    (# | (# fieldLength, fieldStart #) #) -> sliceStep column parser row fieldStart fieldLength++sliceStep :: ColumnNumber -> FieldParser protocol a -> ByteString -> Int -> Int -> (# FieldError | (# a, Int #) #)+sliceStep column parser row start fieldLength =+    if fieldLength > B.length row - start+    then (# FieldError column FieldRowEndsEarly | #)+    else case parserBytes parser (B.unsafeTake fieldLength (B.unsafeDrop start row)) of+        (# kind | #)  -> (# FieldError column kind | #)+        (# | value #) -> (# | (# value, start + fieldLength #) #)++lengthErrorKind :: RowErrorKind -> FieldErrorKind+lengthErrorKind kind = case kind of+    RowEndsEarly                  -> FieldRowEndsEarly+    RowInvalidLengthPrefix prefix -> FieldInvalidLengthPrefix prefix+    RowLengthOverflow             -> FieldLengthOverflow+    RowFieldError fieldError      -> textFieldErrorKind fieldError++-- | A 'FieldDecoder' fitted to one column of a result set.+--+-- @since 1.4.0+data FieldParser (protocol :: Type) a = FieldParser+    { parserNull  :: !(Maybe a)+    , parserBytes :: ParseBytes a+    }++-- | Fits a 'FieldDecoder' to column number @column@, whose definition this is.+-- Prepare once per result set, with the columns+-- 'Database.MySQL.Base.queryRawRows_' returns:+--+-- > (columns, rows) <- queryRawRows_ conn "SELECT emp_no, first_name FROM employees"+-- > numberColumn : nameColumn : _ <- pure columns+-- > numbers <- either throwIO pure (prepareFieldParser int32 (ColumnNumber 0) numberColumn)+-- > names <- either throwIO pure (prepareFieldParser text (ColumnNumber 1) nameColumn)+--+-- @since 1.4.0+prepareFieldParser :: RowProtocol protocol+                   => FieldDecoder a -> ColumnNumber -> ColumnDef -> Either ColumnMismatch (FieldParser protocol a)+prepareFieldParser decoder column definition =+    case protocolParser decoder (columnKind definition) of+        Just parser -> Right parser+        Nothing     -> Left (ColumnTypeMismatch column (columnName definition)+                                (columnType definition) (decoderTypeName decoder))++-- | Decodes field @column@ of a 'RawRow', passing the outcome to a continuation+-- so that no 'Either' is allocated, as a library decoding by column number+-- needs. With the parsers from 'prepareFieldParser', each row's fields chain+-- through the continuations:+--+-- > rawRows <- Streams.toList rows+-- > forM_ rawRows $ \row ->+-- >     runFieldParser numbers row (ColumnNumber 0) throwIO $ \number ->+-- >     runFieldParser names row (ColumnNumber 1) throwIO $ \name ->+-- >     print (number, name)+--+-- @since 1.4.0+runFieldParser :: FieldParser protocol a -> RawRow protocol -> ColumnNumber -> (FieldError -> r) -> (a -> r) -> r+runFieldParser parser row column onError onValue = case parseRawField column parser row of+    (# fieldError | #) -> onError fieldError+    (# | value #)      -> onValue value+{-# INLINE runFieldParser #-}++parseRawField :: ColumnNumber -> FieldParser protocol a -> RawRow protocol -> (# FieldError | a #)+parseRawField column parser row = case rawField row column of+    RawNull -> case parserNull parser of+        Just value -> (# | value #)+        Nothing    -> (# FieldError column FieldUnexpectedNull | #)+    RawBytes fieldBytes -> case parserBytes parser fieldBytes of+        (# kind | #)  -> (# FieldError column kind | #)+        (# | value #) -> (# | value #)+    RawAbsent -> (# FieldError column FieldAbsent | #)++-- | A prepared statement's columns, resolved once per result set for+-- 'decodeBinaryValues'.+--+-- @since 1.4.0+data BinaryValueColumns = BinaryValueColumns !Int [BinaryValueColumn]++data BinaryValueColumn = BinaryValueColumn !ColumnKind !BinaryWidth++-- | @since 1.4.0+binaryValueColumns :: [ColumnDef] -> BinaryValueColumns+binaryValueColumns definitions = BinaryValueColumns (length definitions)+    (map (\definition -> BinaryValueColumn (columnKind definition) (binaryWidth definition)) definitions)++-- | The values of a binary-protocol row, as+-- 'Database.MySQL.Protocol.MySQLValue.getBinaryRow' gives them, in one walk+-- over the row that calls the value parser directly, as 'decodeTextRow' does+-- for the text protocol. Bytes after the last field are ignored.+--+-- @since 1.4.0+decodeBinaryValues :: BinaryValueColumns -> ByteString -> Either FieldError [MySQLValue]+decodeBinaryValues (BinaryValueColumns columnCount columns) row = case binaryRowStart columnCount row of+    (# fieldError | #) -> Left fieldError+    (# | start #) -> case binaryValueFields row (ColumnNumber 0) start columns of+        (# fieldError | #) -> Left fieldError+        (# | values #)     -> Right values++binaryValueFields :: ByteString -> ColumnNumber -> Int -> [BinaryValueColumn] -> (# FieldError | [MySQLValue] #)+binaryValueFields row column offset columns = case columns of+    [] -> (# | [] #)+    BinaryValueColumn kind width : laterColumns ->+        case binaryFieldStep column width (FieldParser (Just MySQLNull) (binaryMySQLValue kind)) row offset of+            (# fieldError | #) -> (# fieldError | #)+            (# | (# value, next #) #) -> case binaryValueFields row (nextColumn column) next laterColumns of+                (# fieldError | #) -> (# fieldError | #)+                (# | values #)     -> (# | value : values #)++-- | A result set that does not fit a 'RowDecoder', raised before any row is read.+--+-- @since 1.4.0+data ColumnMismatch+    = ColumnTypeMismatch !ColumnNumber !ByteString !FieldType !Text+      -- ^ column number, column name, its type, and the Haskell type of the+      -- decoder that does not accept it+    | ColumnCountMismatch !Int !Int+      -- ^ how many columns the decoder reads, how many the result set has+    deriving (Show, Eq)++instance Exception ColumnMismatch++-- | A field that did not decode, with its column number.+--+-- @since 1.4.0+data FieldError = FieldError+    { fieldErrorColumn :: !ColumnNumber+    , fieldErrorKind   :: !FieldErrorKind+    } deriving (Show, Eq)++instance Exception FieldError++-- | @since 1.4.0+data FieldErrorKind+    = FieldUnexpectedNull+      -- ^ a NULL for a decoder that takes none; see 'nullable'+    | FieldUnparsable !ByteString+      -- ^ the bytes are not a value of the column's type+    | FieldInvalidUtf8 !ByteString+    | FieldUnrepresentableDate !RawDate+      -- ^ a date 'day' or 'localTime' cannot give as a 'Day', such as @0000-00-00@;+      -- 'mysqlValue' gives it as a 'MySQLDateRaw' instead+    | FieldNegativeTime !ByteString+      -- ^ a negative TIME for 'timeOfDay'+    | FieldBitTooWide !Int+      -- ^ a BIT field longer than the 8 bytes of a 'Word64'+    | FieldUnsupportedType !FieldType+      -- ^ a column type 'mysqlValue' has no decoder for+    | FieldAbsent+      -- ^ the 'RawRow' has no field with that column number+    | FieldRowEndsEarly+      -- ^ the row ends inside or before this field+    | FieldInvalidLengthPrefix !Word8+      -- ^ a field starting with a byte that is neither a length nor the NULL marker+    | FieldLengthOverflow+      -- ^ an 8-byte length above 'maxBound' of 'Int'+    deriving (Show, Eq)
+ src/Database/MySQL/Field.hs view
@@ -0,0 +1,423 @@+{-|+Module      : Database.MySQL.Field+Description : Convert between Haskell values and 'MySQLValue' fields+Copyright   : (c) Jappie Klooster, 2026+License     : BSD3+Maintainer  : hi@jappie.me+Stability   : experimental+Portability : PORTABLE++A single typeclass, 'Field', for converting an individual Haskell value+to and from a 'MySQLValue'. This is the extension point libraries such as+@beam-mysql@ need to marshal user column types generically: encoding is+total ('toField'), decoding returns 'DecodeError' on the 'Left'+('fromField').++Every instance must obey the roundtrip law:++@+'fromField' ('toField' x) == 'Right' x+@++Two caveats:++* 'Maybe' wrapped around a type that itself encodes to @MySQLNull@+  (@Maybe (Maybe a)@, @Maybe MySQLValue@): SQL cannot represent a+  @Just NULL@ distinct from @NULL@, so both collapse to 'Nothing' on the+  way back. Nullable columns want exactly one 'Maybe'.++* IEEE @NaN@ fails the equation literally because @NaN /= NaN@; the+  'Float' and 'Double' encodings are still lossless and decoding returns+  the same @NaN@.++All instances delegate to named top-level functions (@encodeInt8@,+@decodeInt8@, ...) so the conversions can be used, tested and grepped+without the class.++Decoding is strict and canonical: each Haskell type decodes only from the+constructor(s) carrying exactly its payload, with no implicit numeric+widening or narrowing. There are no instances for 'Int' and 'Word'+because their width is platform dependent; use 'Data.Int.Int64' or+'Data.Word.Word64'.+-}+module Database.MySQL.Field+    ( -- * The Field class+      Field (..)+      -- * Conversion errors+    , DecodeError (..)+    , ExpectedTypeName (..)+    , EncodeStringError (..)+      -- * Named conversions backing the instances+    , encodeInt8, decodeInt8+    , encodeWord8, decodeWord8+    , encodeInt16, decodeInt16+    , encodeWord16, decodeWord16+    , encodeInt32, decodeInt32+    , encodeWord32, decodeWord32+    , encodeInt64, decodeInt64+    , encodeWord64, decodeWord64+    , encodeFloat', decodeFloat'+    , encodeDouble, decodeDouble+    , encodeScientific, decodeScientific+    , encodeText, decodeText+    , encodeLazyText, decodeLazyText+    , encodeString, encodeStringOrCrash, decodeString+    , encodeByteString, decodeByteString+    , encodeLazyByteString, decodeLazyByteString+    , encodeDay, decodeDay+    , encodeLocalTime, decodeLocalTime+    , encodeTimeOfDay, decodeTimeOfDay+    , encodeBool, decodeBool+    , encodeMaybe, decodeMaybe+    ) where++import           Control.Exception                  (Exception)+import           Data.ByteString                    (ByteString)+import qualified Data.ByteString.Lazy               as LazyByteString+import           Data.Int                           (Int16, Int32, Int64, Int8)+import           Data.Scientific                    (Scientific)+import           Data.Text                          (Text)+import qualified Data.Text                          as Text+import qualified Data.Text.Lazy                     as LazyText+import           Data.Time.Calendar                 (Day)+import           Data.Time.LocalTime                (LocalTime, TimeOfDay)+import           Data.Word                          (Word16, Word32, Word64,+                                                     Word8)+import           Database.MySQL.Protocol.MySQLValue (MySQLValue (..))++-- | The name of the Haskell type a decoder was trying to produce,+-- carried inside 'DecodeError' for error reporting.+newtype ExpectedTypeName = ExpectedTypeName Text+    deriving (Show, Eq)++-- | All the ways decoding a single field can fail.+data DecodeError+    = DecodeTypeMismatch !ExpectedTypeName !MySQLValue+      -- ^ The value's constructor does not match the requested Haskell+      -- type, e.g. asking for 'Int32' from a @MySQLText@.+    | DecodeUnexpectedNull !ExpectedTypeName+      -- ^ The value was @MySQLNull@ but the requested type is not+      -- 'Maybe'; wrap the type in 'Maybe' for nullable columns.+    | DecodeUnrepresentableDate !ExpectedTypeName !MySQLValue+      -- ^ A @DATE@, @DATETIME@ or @TIMESTAMP@ whose date the requested type+      -- cannot hold, such as the zero date @0000-00-00@; see+      -- 'Database.MySQL.Protocol.MySQLValue.RawDate'.+    deriving (Show, Eq)++instance Exception DecodeError++-- | Convert a single Haskell value to and from a 'MySQLValue'.+--+-- Instances must obey: @'fromField' ('toField' x) == 'Right' x@.+-- (Caveats: nested 'Maybe' collapses through @NULL@, and IEEE @NaN@+-- fails the literal equation; see the module header.)+class Field a where+    toField :: a -> MySQLValue+    fromField :: MySQLValue -> Either DecodeError a++-- | Build the error for a value whose constructor does not match,+-- mapping @MySQLNull@ to 'DecodeUnexpectedNull' and everything else to+-- 'DecodeTypeMismatch'.+decodeFailure :: ExpectedTypeName -> MySQLValue -> Either DecodeError a+decodeFailure expected MySQLNull = Left (DecodeUnexpectedNull expected)+decodeFailure expected value     = Left (DecodeTypeMismatch expected value)++encodeInt8 :: Int8 -> MySQLValue+encodeInt8 = MySQLInt8++decodeInt8 :: MySQLValue -> Either DecodeError Int8+decodeInt8 (MySQLInt8 n) = Right n+decodeInt8 value         = decodeFailure (ExpectedTypeName "Int8") value++instance Field Int8 where+    toField = encodeInt8+    fromField = decodeInt8++encodeWord8 :: Word8 -> MySQLValue+encodeWord8 = MySQLInt8U++decodeWord8 :: MySQLValue -> Either DecodeError Word8+decodeWord8 (MySQLInt8U n) = Right n+decodeWord8 value          = decodeFailure (ExpectedTypeName "Word8") value++instance Field Word8 where+    toField = encodeWord8+    fromField = decodeWord8++encodeInt16 :: Int16 -> MySQLValue+encodeInt16 = MySQLInt16++decodeInt16 :: MySQLValue -> Either DecodeError Int16+decodeInt16 (MySQLInt16 n) = Right n+decodeInt16 value          = decodeFailure (ExpectedTypeName "Int16") value++instance Field Int16 where+    toField = encodeInt16+    fromField = decodeInt16++encodeWord16 :: Word16 -> MySQLValue+encodeWord16 = MySQLInt16U++decodeWord16 :: MySQLValue -> Either DecodeError Word16+decodeWord16 (MySQLInt16U n) = Right n+decodeWord16 value           = decodeFailure (ExpectedTypeName "Word16") value++instance Field Word16 where+    toField = encodeWord16+    fromField = decodeWord16++encodeInt32 :: Int32 -> MySQLValue+encodeInt32 = MySQLInt32++decodeInt32 :: MySQLValue -> Either DecodeError Int32+decodeInt32 (MySQLInt32 n) = Right n+decodeInt32 value          = decodeFailure (ExpectedTypeName "Int32") value++instance Field Int32 where+    toField = encodeInt32+    fromField = decodeInt32++encodeWord32 :: Word32 -> MySQLValue+encodeWord32 = MySQLInt32U++decodeWord32 :: MySQLValue -> Either DecodeError Word32+decodeWord32 (MySQLInt32U n) = Right n+decodeWord32 value           = decodeFailure (ExpectedTypeName "Word32") value++instance Field Word32 where+    toField = encodeWord32+    fromField = decodeWord32++encodeInt64 :: Int64 -> MySQLValue+encodeInt64 = MySQLInt64++decodeInt64 :: MySQLValue -> Either DecodeError Int64+decodeInt64 (MySQLInt64 n) = Right n+decodeInt64 value          = decodeFailure (ExpectedTypeName "Int64") value++instance Field Int64 where+    toField = encodeInt64+    fromField = decodeInt64++encodeWord64 :: Word64 -> MySQLValue+encodeWord64 = MySQLInt64U++decodeWord64 :: MySQLValue -> Either DecodeError Word64+decodeWord64 (MySQLInt64U n) = Right n+decodeWord64 value           = decodeFailure (ExpectedTypeName "Word64") value++instance Field Word64 where+    toField = encodeWord64+    fromField = decodeWord64++-- | Named with a prime because "Prelude" already exports+-- 'Prelude.encodeFloat'.+encodeFloat' :: Float -> MySQLValue+encodeFloat' = MySQLFloat++decodeFloat' :: MySQLValue -> Either DecodeError Float+decodeFloat' (MySQLFloat x) = Right x+decodeFloat' value          = decodeFailure (ExpectedTypeName "Float") value++instance Field Float where+    toField = encodeFloat'+    fromField = decodeFloat'++encodeDouble :: Double -> MySQLValue+encodeDouble = MySQLDouble++decodeDouble :: MySQLValue -> Either DecodeError Double+decodeDouble (MySQLDouble x) = Right x+decodeDouble value           = decodeFailure (ExpectedTypeName "Double") value++instance Field Double where+    toField = encodeDouble+    fromField = decodeDouble++encodeScientific :: Scientific -> MySQLValue+encodeScientific = MySQLDecimal++decodeScientific :: MySQLValue -> Either DecodeError Scientific+decodeScientific (MySQLDecimal n) = Right n+decodeScientific value            = decodeFailure (ExpectedTypeName "Scientific") value++instance Field Scientific where+    toField = encodeScientific+    fromField = decodeScientific++-- Decision: 'Text' decodes only from @MySQLText@, not from @MySQLBytes@.+-- A @MySQLBytes@ comes from a binary-collated column and need not be valid+-- UTF-8; guessing an encoding here would be a silent failure waiting to+-- happen. Read binary columns as 'ByteString' and decode explicitly.+encodeText :: Text -> MySQLValue+encodeText = MySQLText++decodeText :: MySQLValue -> Either DecodeError Text+decodeText (MySQLText t) = Right t+decodeText value         = decodeFailure (ExpectedTypeName "Text") value++instance Field Text where+    toField = encodeText+    fromField = decodeText++encodeLazyText :: LazyText.Text -> MySQLValue+encodeLazyText = MySQLText . LazyText.toStrict++decodeLazyText :: MySQLValue -> Either DecodeError LazyText.Text+decodeLazyText (MySQLText t) = Right (LazyText.fromStrict t)+decodeLazyText value         = decodeFailure (ExpectedTypeName "lazy Text") value++instance Field LazyText.Text where+    toField = encodeLazyText+    fromField = decodeLazyText++-- | All the ways encoding a 'String' can fail.+data EncodeStringError+    = EncodeStringLoneSurrogate !String+      -- ^ The 'String' contained a lone surrogate code point (@\\xD800@+      -- to @\\xDFFF@), which is not valid Unicode text and cannot be+      -- represented in MySQL's UTF-8 wire format. @Text.pack@ would+      -- silently replace it with @U+FFFD@, corrupting the data.+    deriving (Show, Eq)++instance Exception EncodeStringError++encodeString :: String -> Either EncodeStringError MySQLValue+encodeString string =+    if any isLoneSurrogate string+        then Left (EncodeStringLoneSurrogate string)+        else Right (MySQLText (Text.pack string))++isLoneSurrogate :: Char -> Bool+isLoneSurrogate character = '\xD800' <= character && character <= '\xDFFF'++-- | Backs the 'Field' 'String' instance: the class fixes+-- @toField :: a -> MySQLValue@, so the 'EncodeStringLoneSurrogate'+-- failure from 'encodeString' crashes here instead of being returned.+encodeStringOrCrash :: String -> MySQLValue+encodeStringOrCrash string = case encodeString string of+    Right value -> value+    Left (EncodeStringLoneSurrogate offending) ->+        error ("Database.MySQL.Field.encodeString: \+               \lone surrogate code point in String: " <> show offending+               <> ". This String is not valid Unicode, so the bug is \+                  \in whatever produced it (truncated UTF-16 or bad \+                  \decoding upstream). Fix that producer, or filter \+                  \the surrogates out before calling toField.")++decodeString :: MySQLValue -> Either DecodeError String+decodeString (MySQLText t) = Right (Text.unpack t)+decodeString value         = decodeFailure (ExpectedTypeName "String") value++instance Field String where+    toField = encodeStringOrCrash+    fromField = decodeString++encodeByteString :: ByteString -> MySQLValue+encodeByteString = MySQLBytes++decodeByteString :: MySQLValue -> Either DecodeError ByteString+decodeByteString (MySQLBytes bs) = Right bs+decodeByteString value           = decodeFailure (ExpectedTypeName "ByteString") value++instance Field ByteString where+    toField = encodeByteString+    fromField = decodeByteString++encodeLazyByteString :: LazyByteString.ByteString -> MySQLValue+encodeLazyByteString = MySQLBytes . LazyByteString.toStrict++decodeLazyByteString :: MySQLValue -> Either DecodeError LazyByteString.ByteString+decodeLazyByteString (MySQLBytes bs) = Right (LazyByteString.fromStrict bs)+decodeLazyByteString value           = decodeFailure (ExpectedTypeName "lazy ByteString") value++instance Field LazyByteString.ByteString where+    toField = encodeLazyByteString+    fromField = decodeLazyByteString++encodeDay :: Day -> MySQLValue+encodeDay = MySQLDate++decodeDay :: MySQLValue -> Either DecodeError Day+decodeDay (MySQLDate d)          = Right d+decodeDay value@(MySQLDateRaw _) = Left (DecodeUnrepresentableDate (ExpectedTypeName "Day") value)+decodeDay value                  = decodeFailure (ExpectedTypeName "Day") value++instance Field Day where+    toField = encodeDay+    fromField = decodeDay++-- Decision: 'LocalTime' decodes from both @MySQLDateTime@ and+-- @MySQLTimeStamp@, since both carry a 'LocalTime' payload and which one+-- arrives depends on the column type, not on the Haskell type. Encoding+-- canonically produces @MySQLDateTime@; the roundtrip law still holds.+encodeLocalTime :: LocalTime -> MySQLValue+encodeLocalTime = MySQLDateTime++decodeLocalTime :: MySQLValue -> Either DecodeError LocalTime+decodeLocalTime (MySQLDateTime t)  = Right t+decodeLocalTime (MySQLTimeStamp t) = Right t+decodeLocalTime value@(MySQLDateTimeRaw _ _) =+    Left (DecodeUnrepresentableDate (ExpectedTypeName "LocalTime") value)+decodeLocalTime value@(MySQLTimeStampRaw _ _) =+    Left (DecodeUnrepresentableDate (ExpectedTypeName "LocalTime") value)+decodeLocalTime value              = decodeFailure (ExpectedTypeName "LocalTime") value++instance Field LocalTime where+    toField = encodeLocalTime+    fromField = decodeLocalTime++-- | A 'TimeOfDay' is only the non-negative, less-than-a-day fragment of+-- MySQL's @TIME@ type; a negative @TIME@ value fails to decode. For the+-- full range (intervals up to +-838:59:59) match on @MySQLTime@ directly.+encodeTimeOfDay :: TimeOfDay -> MySQLValue+encodeTimeOfDay = MySQLTime 0++decodeTimeOfDay :: MySQLValue -> Either DecodeError TimeOfDay+decodeTimeOfDay (MySQLTime sign t) =+    if sign == 0+        then Right t+        else decodeFailure (ExpectedTypeName "TimeOfDay") (MySQLTime sign t)+decodeTimeOfDay value = decodeFailure (ExpectedTypeName "TimeOfDay") value++instance Field TimeOfDay where+    toField = encodeTimeOfDay+    fromField = decodeTimeOfDay++-- Decision: 'Bool' maps to @TINYINT@, mirroring MySQL itself where+-- @BOOLEAN@ is an alias for @TINYINT(1)@ and any nonzero value is true.+-- Both the signed and unsigned tiny constructors are accepted because the+-- column may be declared either way.+encodeBool :: Bool -> MySQLValue+encodeBool True  = MySQLInt8 1+encodeBool False = MySQLInt8 0++decodeBool :: MySQLValue -> Either DecodeError Bool+decodeBool (MySQLInt8 n)  = Right (n /= 0)+decodeBool (MySQLInt8U n) = Right (n /= 0)+decodeBool value          = decodeFailure (ExpectedTypeName "Bool") value++instance Field Bool where+    toField = encodeBool+    fromField = decodeBool++encodeMaybe :: Field a => Maybe a -> MySQLValue+encodeMaybe Nothing      = MySQLNull+encodeMaybe (Just value) = toField value++decodeMaybe :: Field a => MySQLValue -> Either DecodeError (Maybe a)+decodeMaybe MySQLNull = Right Nothing+decodeMaybe value     = fmap Just (fromField value)++-- | @NULL@ maps to 'Nothing'; use this instance for nullable columns.+-- Do not nest it: SQL cannot distinguish @Just NULL@ from @NULL@, so+-- @Just Nothing@ decodes back as 'Nothing' and the roundtrip law breaks.+instance Field a => Field (Maybe a) where+    toField = encodeMaybe+    fromField = decodeMaybe++-- | The identity instance, an escape hatch for code that wants to handle+-- the raw protocol value in an otherwise 'Field'-polymorphic setting.+instance Field MySQLValue where+    toField = id+    fromField = Right
+ src/Database/MySQL/Protocol/Auth.hs view
@@ -0,0 +1,295 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -funbox-strict-fields #-}++{-|+Module      : Database.MySQL.Protocol.Auth+Description : MySQL Auth Packets+Copyright   : (c) Winterland, 2016+License     : BSD+Maintainer  : drkoster@qq.com+Stability   : experimental+Portability : PORTABLE++Auth related packet.++-}++module Database.MySQL.Protocol.Auth where++import           Control.Monad+import           Data.Binary+import           Data.Binary.Get+import           Data.Binary.Parser+import           Data.Binary.Put+import qualified Data.ByteString                as B+import           Data.ByteString.Char8          as BC+import           Data.Bits+import           Database.MySQL.Protocol.Packet++--------------------------------------------------------------------------------+-- Authentications++#define CLIENT_LONG_PASSWORD                  0x00000001+#define CLIENT_FOUND_ROWS                     0x00000002+#define CLIENT_LONG_FLAG                      0x00000004+#define CLIENT_CONNECT_WITH_DB                0x00000008+#define CLIENT_NO_SCHEMA                      0x00000010+#define CLIENT_COMPRESS                       0x00000020+#define CLIENT_ODBC                           0x00000040+#define CLIENT_LOCAL_FILES                    0x00000080+#define CLIENT_IGNORE_SPACE                   0x00000100+#define CLIENT_PROTOCOL_41                    0x00000200+#define CLIENT_INTERACTIVE                    0x00000400+#define CLIENT_SSL                            0x00000800+#define CLIENT_IGNORE_SIGPIPE                 0x00001000+#define CLIENT_TRANSACTIONS                   0x00002000+#define CLIENT_RESERVED                       0x00004000+#define CLIENT_SECURE_CONNECTION              0x00008000+#define CLIENT_MULTI_STATEMENTS               0x00010000+#define CLIENT_MULTI_RESULTS                  0x00020000+#define CLIENT_PS_MULTI_RESULTS               0x00040000+#define CLIENT_PLUGIN_AUTH                    0x00080000+#define CLIENT_CONNECT_ATTRS                  0x00100000+#define CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA 0x00200000++-- MariaDB reuses the obsolete CLIENT_LONG_PASSWORD bit to mean "MySQL client+-- or server". See Note [MariaDB extended capabilities].+#define CLIENT_MYSQL                          CLIENT_LONG_PASSWORD++{- Note [MariaDB extended capabilities]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+MariaDB has capabilities beyond the 32 bits of the MySQL handshake+(MARIADB_CLIENT_* in mariadb-connector-c's mariadb_com.h, bits 32 to 36). A+MariaDB server leaves CLIENT_MYSQL out of its greeting and puts the upper 32+bits in the last four of the greeting's ten reserved bytes. A client answers+with its own upper bits in the last four of the 23 reserved bytes of the SSL+request and the handshake response, and must leave CLIENT_MYSQL out as well,+or the server ignores those bytes.++mysql-haskell sets CLIENT_MYSQL by default and writes zeroes there, so a+MariaDB server treats it as a MySQL client. Only a connection that asks for an+extended capability, to a server that offers it, sends anything different:+'negotiateExtended' yields 'noExtendedCapabilities' otherwise, and every+encoder below writes exactly the bytes it wrote before extended capabilities+existed. A MySQL server always sets CLIENT_MYSQL, so it never sees the change.+-}++-- | MariaDB's capabilities above bit 31, shifted down by 32, so bit 0 here is+-- MARIADB_CLIENT_PROGRESS. See Note [MariaDB extended capabilities].+--+-- @since 1.4.0+newtype ExtendedCapabilities = ExtendedCapabilities Word32 deriving (Show, Eq)++-- | What every connection sent before extended capabilities existed.+--+-- @since 1.4.0+noExtendedCapabilities :: ExtendedCapabilities+noExtendedCapabilities = ExtendedCapabilities 0++-- | The extended capabilities a connection uses: those the client asked for+-- that the server offered, or none when the server is not a MariaDB server+-- reading them.+--+-- @since 1.4.0+negotiateExtended :: ExtendedCapabilities  -- ^ requested by the client+                  -> Greeting+                  -> ExtendedCapabilities  -- ^ offered in that greeting+                  -> ExtendedCapabilities+negotiateExtended (ExtendedCapabilities requested) greeting (ExtendedCapabilities offered) =+    if offersExtendedCapabilities greeting+    then ExtendedCapabilities (requested .&. offered)+    else noExtendedCapabilities++-- | Whether the greeting comes from a server speaking MariaDB's protocol+-- extensions: MariaDB 10.2 and later leave CLIENT_MYSQL out of it, MySQL and+-- older MariaDB set it. See Note [MariaDB extended capabilities].+--+-- @since 1.4.0+offersExtendedCapabilities :: Greeting -> Bool+offersExtendedCapabilities greeting = greetingCaps greeting .&. CLIENT_MYSQL == 0++data Greeting = Greeting+    { greetingProtocol :: !Word8+    , greetingVersion  :: !B.ByteString+    , greetingConnId   :: !Word32+    , greetingSalt1    :: !B.ByteString+    , greetingCaps     :: !Word32+    , greetingCharset  :: !Word8+    , greetingStatus   :: !Word16+    , greetingSalt2    :: !B.ByteString+    , greetingAuthPlugin :: !B.ByteString+    } deriving (Show, Eq)++putGreeting :: Greeting -> Put+putGreeting (Greeting pv sv cid salt1 cap charset st salt2 authPlugin) = do+    putWord8 pv+    putByteString sv+    putWord8 0x00+    putWord32le cid+    putByteString salt1+    let capL = fromIntegral cap .|. 0xFF+        capH = fromIntegral (cap `shiftR` 16) .|. 0xFF+    putWord16le capL+    putWord8 charset+    putWord16le st+    putWord16le capH+    putWord8 (fromIntegral $ B.length salt2)+    replicateM_ 10 (putWord8 0x00)+    when (cap .&. CLIENT_SECURE_CONNECTION /= 0)+        (putByteString salt2)+    when (cap .&. CLIENT_PLUGIN_AUTH /= 0)+        (putByteString authPlugin)++getGreeting :: Get Greeting+getGreeting = fst <$> getGreetingExtended++-- | 'getGreeting' together with the extended capabilities a MariaDB server+-- offers; MySQL servers send zeroes there.+--+-- @since 1.4.0+getGreetingExtended :: Get (Greeting, ExtendedCapabilities)+getGreetingExtended = do+    pv <- getWord8+    sv <- getByteStringNul+    cid <- getWord32le+    salt1 <- getByteString 8+    skipN 1  -- 0x00+    capL <- getWord16le+    charset <- getWord8+    status <- getWord16le+    capH <- getWord16le+    let cap = fromIntegral capH `shiftL` 16 .|. fromIntegral capL+    _authPluginLen <- getWord8   -- this will issue an unused warning, see the notes below+    skipN 6+    extended <- ExtendedCapabilities <$> getWord32le+    salt2 <- if (cap .&. CLIENT_SECURE_CONNECTION) == 0+        then pure B.empty+        else getByteStringNul   -- This is different with the MySQL document here+                                -- The doc said we should expect a MAX(13, length of auth-plugin-data - 8)+                                -- length bytes, but doing so stop us from login+                                -- anyway 'getByteStringNul' works perfectly here.++    authPlugin <- if (cap .&. CLIENT_PLUGIN_AUTH) == 0+        then pure B.empty+        else getByteStringNul++    return (Greeting pv sv cid salt1 cap charset status salt2 authPlugin, extended)++instance Binary Greeting where+    get = getGreeting+    put = putGreeting++data Auth = Auth+    { authCaps      :: !Word32+    , authMaxPacket :: !Word32+    , authCharset   :: !Word8+    , authName      :: !ByteString+    , authPassword  :: !ByteString+    , authSchema    :: !ByteString+    , authPlugin    :: !ByteString+    } deriving (Show, Eq)++getAuth :: Get Auth+getAuth = do+    a <- getWord32le+    m <- getWord32le+    c <- getWord8+    skipN 23+    n <- getByteStringNul+    return $ Auth a m c n B.empty B.empty B.empty++putAuth :: Auth -> Put+putAuth = putAuthExtended noExtendedCapabilities++-- | 'putAuth' carrying negotiated extended capabilities; 'authCaps' must then+-- leave CLIENT_MYSQL out, see 'clientCapFor'.+--+-- @since 1.4.0+putAuthExtended :: ExtendedCapabilities -> Auth -> Put+putAuthExtended (ExtendedCapabilities extended) (Auth cap m c n p s plugin) = do+    putWord32le cap+    putWord32le m+    putWord8 c+    replicateM_ 19 (putWord8 0x00)+    putWord32le extended+    putByteString n >> putWord8 0x00+    putWord8 $ fromIntegral (B.length p)+    putByteString p+    putByteString s+    putWord8 0x00+    putByteString plugin+    putWord8 0x00++instance Binary Auth where+    get = getAuth+    put = putAuth++data SSLRequest = SSLRequest+    { sslReqCaps      :: !Word32+    , sslReqMaxPacket :: !Word32+    , sslReqCharset   :: !Word8+    } deriving (Show, Eq)++getSSLRequest :: Get SSLRequest+getSSLRequest = SSLRequest <$> getWord32le <*> getWord32le <*> getWord8 <* skipN 23++putSSLRequest :: SSLRequest -> Put+putSSLRequest = putSSLRequestExtended noExtendedCapabilities++-- | 'putSSLRequest' carrying negotiated extended capabilities. MariaDB reads+-- them from the SSL request too, before the TLS handshake.+--+-- @since 1.4.0+putSSLRequestExtended :: ExtendedCapabilities -> SSLRequest -> Put+putSSLRequestExtended (ExtendedCapabilities extended) (SSLRequest cap m c) = do+    putWord32le cap+    putWord32le m+    putWord8 c+    replicateM_ 19 (putWord8 0x00)+    putWord32le extended++instance Binary SSLRequest where+    get = getSSLRequest+    put = putSSLRequest++--------------------------------------------------------------------------------+-- default Capability Flags++clientCap :: Word32+clientCap =  CLIENT_LONG_PASSWORD+                .|. CLIENT_LONG_FLAG+                .|. CLIENT_CONNECT_WITH_DB+                .|. CLIENT_IGNORE_SPACE+                .|. CLIENT_PROTOCOL_41+                .|. CLIENT_TRANSACTIONS+                .|. CLIENT_MULTI_STATEMENTS+                .|. CLIENT_MULTI_RESULTS+                .|. CLIENT_SECURE_CONNECTION+                .|. CLIENT_PLUGIN_AUTH++-- | 'clientCap' for a connection using these extended capabilities.+--+-- @since 1.4.0+clientCapFor :: ExtendedCapabilities -> Word32+clientCapFor negotiated =+    if negotiated == noExtendedCapabilities+    then clientCap+    else clientCap .&. complement CLIENT_MYSQL++clientMaxPacketSize :: Word32+clientMaxPacketSize = 0x00ffffff :: Word32+++supportTLS :: Word32 -> Bool+supportTLS x = (x .&. CLIENT_SSL) /= 0++sslRequest :: Word8 -> SSLRequest+sslRequest = sslRequestFor noExtendedCapabilities++-- | 'sslRequest' for a connection using these extended capabilities; encode+-- it with 'putSSLRequestExtended'.+--+-- @since 1.4.0+sslRequestFor :: ExtendedCapabilities -> Word8 -> SSLRequest+sslRequestFor negotiated charset =+    SSLRequest (clientCapFor negotiated .|. CLIENT_SSL) clientMaxPacketSize charset
+ src/Database/MySQL/Protocol/ColumnDef.hs view
@@ -0,0 +1,218 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -funbox-strict-fields #-}++{-|+Module      : Database.MySQL.Protocol.ColumnDef+Description : MySQL field type+Copyright   : (c) Winterland, 2016+License     : BSD+Maintainer  : drkoster@qq.com+Stability   : experimental+Portability : PORTABLE++Column definition(aka. field type).++-}++module Database.MySQL.Protocol.ColumnDef where++import           Data.Binary+import           Data.Binary.Get+import           Data.Binary.Parser+import           Data.Binary.Put+import           Data.Bits                      ((.&.))+import           Data.ByteString                (ByteString)+import qualified Data.ByteString                as B+import           Database.MySQL.Protocol.Packet++--------------------------------------------------------------------------------+--  Resultset++-- | A description of a field (column) of a table.+data ColumnDef = ColumnDef+    { -- fieldCatalog :: !ByteString              -- ^ const 'def'+      columnDB        ::  !ByteString             -- ^ Database for table.+    , columnTable     ::  !ByteString             -- ^ Table of column, if column was a field.+    , columnOrigTable ::  !ByteString             -- ^ Original table name, if table was an alias.+    , columnName      ::  !ByteString             -- ^ Name of column.+    , columnOrigName  ::  !ByteString             -- ^ Original column name, if an alias.+    , columnCharSet   ::  !Word16                 -- ^ Character set number.+    , columnLength    ::  !Word32                 -- ^ Width of column (create length).+    , columnType      ::  !FieldType+    , columnFlags     ::  !Word16                 -- ^ Div flags.+    , columnDecimals  ::  !Word8                  -- ^ Number of decimals in field.+    } deriving (Show, Eq)++getField :: Get ColumnDef+getField = fst <$> getFieldExtended+{-# INLINE getField #-}++-- | 'getField' together with the raw MariaDB extended metadata, empty when the+-- server sent none. See Note [Extended column metadata].+--+-- @since 1.4.0+getFieldExtended :: Get (ColumnDef, ByteString)+getFieldExtended = do+    skipN 4                         -- const "def"+    db        <- getLenEncBytes+    table     <- getLenEncBytes+    origTable <- getLenEncBytes+    name      <- getLenEncBytes+    origName  <- getLenEncBytes+    extended  <- getExtendedMetadataBlock+    skipN 1                         -- const 0x0c+    column <- ColumnDef db table origTable name origName+        <$> getWord16le             -- charset+        <*> getWord32le             -- length+        <*> getFieldType            -- type+        <*> getWord16le             -- flags+        <*> getWord8                -- decimals+    skipN 2                         -- const 0x00 0x00+    pure (column, extended)+{-# INLINE getFieldExtended #-}++{- Note [Extended column metadata]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+A MariaDB connection that negotiated MARIADB_CLIENT_EXTENDED_METADATA gets a+length-encoded string between the column names and the fixed fields, holding+type names such as "json" or "uuid". The connection does not record what it+negotiated (MySQLConn's fields are public), so the parser recognises the block+from the packet instead: without it, the 0x0c that starts the fixed fields is+followed by exactly 12 bytes, which is how both MySQL and MariaDB end every+column definition of a result set or a prepared statement. A block of 12 bytes+also starts with 0x0c, but then 25 bytes follow. Column definitions from the+deprecated COM_FIELD_LIST carry default values after the fixed fields; this+library never sends that command.+-}++-- Decision: detect the block from the packet length rather than carry the+-- negotiated capabilities to every parser. Carrying them needs a new field in+-- MySQLConn, whose constructor callers pattern match on, and threads a flag+-- through each query function; the length test changes nothing for+-- connections that did not negotiate the block.+getExtendedMetadataBlock :: Get ByteString+getExtendedMetadataBlock = do+    fixedFieldsNext <- lookAhead startsFixedFields+    if fixedFieldsNext then pure B.empty else getLenEncBytes++startsFixedFields :: Get Bool+startsFixedFields = do+    marker <- getWord8+    if marker == 0x0c+    then skipN 12 *> isEmpty+    else pure False++putField :: ColumnDef -> Put+putField (ColumnDef db tbl otbl name oname charset len typ flags dec) = do+    putLenEncBytes "def"+    putLenEncBytes db+    putLenEncBytes tbl+    putLenEncBytes otbl+    putLenEncBytes name+    putLenEncBytes oname+    putWord16le charset+    putWord32le len+    putFieldType typ+    putWord16le  flags+    putWord8 dec+    putWord16le 0X0000+{-# INLINE putField #-}++instance Binary ColumnDef where+    get = getField+    {-# INLINE get #-}+    put = putField+    {-# INLINE put #-}++-- | @newtype@ around 'Word8' for represent @MySQL_TYPE@, We don't use sum type here for speed reason.+--+newtype FieldType = FieldType Word8 deriving (Show, Eq)++mySQLTypeDecimal, mySQLTypeTiny, mySQLTypeShort, mySQLTypeLong, mySQLTypeFloat :: FieldType+mySQLTypeDouble, mySQLTypeNull, mySQLTypeTimestamp, mySQLTypeLongLong, mySQLTypeInt24 :: FieldType+mySQLTypeDate, mySQLTypeTime, mySQLTypeDateTime, mySQLTypeYear, mySQLTypeNewDate, mySQLTypeVarChar :: FieldType+mySQLTypeBit, mySQLTypeTimestamp2, mySQLTypeDateTime2, mySQLTypeTime2, mySQLTypeNewDecimal :: FieldType+mySQLTypeEnum, mySQLTypeSet, mySQLTypeTinyBlob, mySQLTypeMediumBlob, mySQLTypeLongBlob :: FieldType+mySQLTypeBlob, mySQLTypeVarString, mySQLTypeString, mySQLTypeGeometry :: FieldType++mySQLTypeDecimal        = FieldType 0x00+mySQLTypeTiny           = FieldType 0x01+mySQLTypeShort          = FieldType 0x02+mySQLTypeLong           = FieldType 0x03+mySQLTypeFloat          = FieldType 0x04+mySQLTypeDouble         = FieldType 0x05+mySQLTypeNull           = FieldType 0x06+mySQLTypeTimestamp      = FieldType 0x07+mySQLTypeLongLong       = FieldType 0x08+mySQLTypeInt24          = FieldType 0x09+mySQLTypeDate           = FieldType 0x0a+mySQLTypeTime           = FieldType 0x0b+mySQLTypeDateTime       = FieldType 0x0c+mySQLTypeYear           = FieldType 0x0d+mySQLTypeNewDate        = FieldType 0x0e+mySQLTypeVarChar        = FieldType 0x0f+mySQLTypeBit            = FieldType 0x10+mySQLTypeTimestamp2     = FieldType 0x11+mySQLTypeDateTime2      = FieldType 0x12+mySQLTypeTime2          = FieldType 0x13+mySQLTypeNewDecimal     = FieldType 0xf6+mySQLTypeEnum           = FieldType 0xf7+mySQLTypeSet            = FieldType 0xf8+mySQLTypeTinyBlob       = FieldType 0xf9+mySQLTypeMediumBlob     = FieldType 0xfa+mySQLTypeLongBlob       = FieldType 0xfb+mySQLTypeBlob           = FieldType 0xfc+mySQLTypeVarString      = FieldType 0xfd+mySQLTypeString         = FieldType 0xfe+mySQLTypeGeometry       = FieldType 0xff++getFieldType :: Get FieldType+getFieldType = FieldType <$> getWord8+{-# INLINE getFieldType #-}++putFieldType :: FieldType -> Put+putFieldType (FieldType t) = putWord8 t+{-# INLINE putFieldType #-}++instance Binary FieldType where+    get = getFieldType+    {-# INLINE get #-}+    put = putFieldType+    {-# INLINE put #-}++--------------------------------------------------------------------------------+--  Field flags++#define NOT_NULL_FLAG         1+#define PRI_KEY_FLAG          2+#define UNIQUE_KEY_FLAG       4+#define MULT_KEY_FLAG         8+#define BLOB_FLAG             16+#define UNSIGNED_FLAG         32+#define ZEROFILL_FLAG         64+#define BINARY_FLAG           128+#define ENUM_FLAG             256+#define AUTO_INCREMENT_FLAG   512+#define TIMESTAMP_FLAG        1024+#define SET_FLAG              2048+#define NO_DEFAULT_VALUE_FLAG 4096+#define PART_KEY_FLAG         16384+#define NUM_FLAG              32768++flagNotNull, flagPrimaryKey, flagUniqueKey, flagMultipleKey, flagBlob, flagUnsigned, flagZeroFill :: Word16 -> Bool+flagBinary, flagEnum, flagAutoIncrement, flagTimeStamp, flagSet, flagNoDefaultValue, flagPartKey, flagNumeric :: Word16 -> Bool+flagNotNull        flags = flags .&. NOT_NULL_FLAG         == NOT_NULL_FLAG+flagPrimaryKey     flags = flags .&. PRI_KEY_FLAG          == PRI_KEY_FLAG+flagUniqueKey      flags = flags .&. UNIQUE_KEY_FLAG       == UNIQUE_KEY_FLAG+flagMultipleKey    flags = flags .&. MULT_KEY_FLAG         == MULT_KEY_FLAG+flagBlob           flags = flags .&. BLOB_FLAG             == BLOB_FLAG+flagUnsigned       flags = flags .&. UNSIGNED_FLAG         == UNSIGNED_FLAG+flagZeroFill       flags = flags .&. ZEROFILL_FLAG         == ZEROFILL_FLAG+flagBinary         flags = flags .&. BINARY_FLAG           == BINARY_FLAG+flagEnum           flags = flags .&. ENUM_FLAG             == ENUM_FLAG+flagAutoIncrement  flags = flags .&. AUTO_INCREMENT_FLAG   == AUTO_INCREMENT_FLAG+flagTimeStamp      flags = flags .&. TIMESTAMP_FLAG        == TIMESTAMP_FLAG+flagSet            flags = flags .&. SET_FLAG              == SET_FLAG+flagNoDefaultValue flags = flags .&. NO_DEFAULT_VALUE_FLAG == NO_DEFAULT_VALUE_FLAG+flagPartKey        flags = flags .&. PART_KEY_FLAG         == PART_KEY_FLAG+flagNumeric        flags = flags .&. NUM_FLAG              == NUM_FLAG
+ src/Database/MySQL/Protocol/Command.hs view
@@ -0,0 +1,107 @@+{-# OPTIONS_GHC -funbox-strict-fields #-}++{-|+Module      : Database.MySQL.Protocol.Command+Description : MySQL commands+Copyright   : (c) Winterland, 2016+License     : BSD+Maintainer  : drkoster@qq.com+Stability   : experimental+Portability : PORTABLE++Common MySQL commands supports.++-}++module Database.MySQL.Protocol.Command where++import           Control.Monad+import           Data.Binary+import           Data.Binary.Get+import           Data.Binary.Parser+import           Data.Binary.Put+import           Data.ByteString                    (ByteString)+import qualified Data.ByteString.Lazy               as L+import           Database.MySQL.Protocol.MySQLValue+import           Database.MySQL.Protocol.Packet++--------------------------------------------------------------------------------+--  Commands++type StmtID = Word32++-- | All support MySQL commands.+--+data Command+    = COM_QUIT                                    -- ^ 0x01+    | COM_INIT_DB        !ByteString              -- ^ 0x02+    | COM_QUERY          !L.ByteString            -- ^ 0x03+    | COM_PING                                    -- ^ 0x0E+    | COM_BINLOG_DUMP    !Word32 !Word16 !Word32 !ByteString -- ^ 0x12+            -- binlog-pos, flags(0x01), server-id, binlog-filename+    | COM_REGISTER_SLAVE !Word32 !ByteString !ByteString !ByteString !Word16 !Word32 !Word32 -- ^ 0x15+            -- server-id, slaves hostname, slaves user, slaves password,  slaves port, replication rank(ignored), master-id(usually 0)+    | COM_STMT_PREPARE   !L.ByteString            -- ^ 0x16 statement+    | COM_STMT_EXECUTE   !StmtID ![MySQLValue] !BitMap -- ^ 0x17 stmtId, params+    | COM_STMT_CLOSE     !StmtID                  -- ^ 0x19 stmtId+    | COM_STMT_RESET     !StmtID                  -- ^ 0x1A stmtId+    | COM_UNSUPPORTED+   deriving (Show, Eq)++putCommand :: Command -> Put+putCommand COM_QUIT              = putWord8 0x01+putCommand (COM_INIT_DB db)      = putWord8 0x02 >> putByteString db+putCommand (COM_QUERY q)         = putWord8 0x03 >> putLazyByteString q+putCommand COM_PING              = putWord8 0x0E+putCommand (COM_BINLOG_DUMP pos flags sid fname) = do+    putWord8 0x12+    putWord32le pos+    putWord16le flags+    putWord32le sid+    putByteString fname+putCommand (COM_REGISTER_SLAVE sid shost susr spass sport rrank mid) = do+    putWord8 0x15+    putWord32le sid+    putLenEncBytes shost+    putLenEncBytes susr+    putLenEncBytes spass+    putWord16le sport+    putWord32le rrank+    putWord32le mid+putCommand (COM_STMT_PREPARE stmt) = putWord8 0x16 >> putLazyByteString stmt+putCommand (COM_STMT_EXECUTE stid params nullmap) = do+    putWord8 0x17+    putWord32le stid+    putWord8 0x00 -- we only use @CURSOR_TYPE_NO_CURSOR@ here+    putWord32le 1 -- const 1+    unless (null params) $ do+        putByteString (fromBitMap nullmap)+        putWord8 0x01    -- always use new-params-bound-flag+        mapM_ putParamMySQLType params+        forM_ params putBinaryField++putCommand (COM_STMT_CLOSE stid) = putWord8 0x19 >> putWord32le stid+putCommand (COM_STMT_RESET stid) = putWord8 0x1A >> putWord32le stid+putCommand _                     = error "unsupported command"++--------------------------------------------------------------------------------+--  Prepared statment related++-- | call 'isOK' with this packet return true+data StmtPrepareOK = StmtPrepareOK+    { stmtId        :: !StmtID+    , stmtColumnCnt :: !Int+    , stmtParamCnt  :: !Int+    , stmtWarnCnt   :: !Int+    } deriving (Show, Eq)++getStmtPrepareOK :: Get StmtPrepareOK+getStmtPrepareOK = do+    skipN 1 -- OK byte+    stmtid <- getWord32le+    cc <- fromIntegral <$> getWord16le+    pc <- fromIntegral <$> getWord16le+    skipN 1 -- reserved+    wc <- fromIntegral <$> getWord16le+    return (StmtPrepareOK stmtid cc pc wc)+{-# INLINE getStmtPrepareOK #-}
+ src/Database/MySQL/Protocol/Escape.hs view
@@ -0,0 +1,117 @@+{-# LANGUAGE BangPatterns #-}++{-|+Module      : Database.MySQL.Protocol.Escape+Description : Pure haskell mysql escape+Copyright   : (c) Winterland, 2016+License     : BSD+Maintainer  : drkoster@qq.com+Stability   : experimental+Portability : PORTABLE++Escaping for the body of a single-quoted string literal, the form+'Database.MySQL.Protocol.MySQLValue.putTextField' renders text and bytes in.+The result is only safe between single quotes: a double quote is not escaped.++A quote is doubled and a backslash escaped with a backslash; every other+character is kept as it is. Under @NO_BACKSLASH_ESCAPES@ the literal reads the+same, except that each backslash is stored twice.++Not safe on a connection whose character set lets a multibyte character end+in the backslash byte, 0x5C: gbk, big5, sjis, cp932 and gb18030. There the+escaping backslash can become the second byte of a character, and the+backslash in the data then escapes a quote, so a parameter can end its+literal, with @SET NAMES gbk@ on MySQL 8.0.45 and MariaDB 11.4.9 alike. UTF-8,+the default, and latin1 never put 0x5C inside a character. On those other+character sets, use prepared statements, which send parameters separately.++reference: <https://dev.mysql.com/doc/refman/8.0/en/string-literals.html>++-}++module Database.MySQL.Protocol.Escape (escapeText, escapeBytes) where++import           Control.Monad.ST         (ST)+import           Data.ByteString          (ByteString)+import qualified Data.ByteString.Internal as B+import           Data.Text                (Text)+import qualified Data.Text.Array          as TA+import qualified Data.Text.Internal       as T+import           Data.Word+import           Foreign.ForeignPtr       (withForeignPtr)+import           Foreign.Ptr              (Ptr, minusPtr, plusPtr)+import           Foreign.Storable         (peek, poke, pokeByteOff)+import           GHC.IO                   (unsafeDupablePerformIO)++-- Decision: escape only the quote, by doubling it, and the backslash, with a+-- backslash: the MySQL manual says only these two must be escaped in a+-- single-quoted literal. Escaping the quote as @\'@ ended the literal under+-- @NO_BACKSLASH_ESCAPES@, and the other escapes mysql_real_escape_string+-- writes (@\0 \n \r \Z \"@) only make logs readable while storing a+-- stray backslash in that mode. test/ParameterEscaping.hs round-trips every+-- byte 0x00 to 0xFF raw on MariaDB and MySQL. See Note [Backslashes without+-- backslash escapes].++-- Note [Backslashes without backslash escapes]+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+-- Under @NO_BACKSLASH_ESCAPES@ a backslash is an ordinary character, so+-- @C:\@, rendered as @'C:\\'@, is stored as @C:\\@. Leaving the backslash+-- unescaped is no way out: in the default mode @'C:\'@ escapes its own+-- closing quote. Rendering for the session's mode needs the+-- SERVER_STATUS_NO_BACKSLASH_ESCAPES flag of the server's OK packets, kept+-- per connection. 'Database.MySQL.Connection.MySQLConn' and its+-- 'Database.MySQL.Connection.ReplyState' are exported with their+-- constructors, so a place for that flag changes the API: that waits for a+-- major release.++-- | Escape text for a single-quoted literal, see the module header.+escapeText :: Text -> Text+escapeText (T.Text array offset len) = if len <= 0+    then T.empty+    else+        let (escaped, escapedLength) = TA.run2 $ do+                target <- TA.new (len * 2)+                escapeUnits array (offset + len) target offset 0+        in T.Text escaped 0 escapedLength++-- | Copies the code units from @source@ to @sourceEnd@ into @target@, from+-- @targetIndex@ on, doubling quotes and backslashes; it returns @target@ and+-- the index after the last unit written. A unit is a byte of UTF-8 (text 2)+-- or a 16-bit unit of UTF-16 (text 1); neither encoding puts a quote or a+-- backslash inside a longer character, so units can be copied one by one.+escapeUnits :: TA.Array -> Int -> TA.MArray s -> Int -> Int -> ST s (TA.MArray s, Int)+escapeUnits array sourceEnd target !source !targetIndex = if source == sourceEnd+    then return (target, targetIndex)+    else do+        let unit = TA.unsafeIndex array source+        TA.unsafeWrite target targetIndex unit+        if unit == quote || unit == backslash+            then do+                TA.unsafeWrite target (targetIndex + 1) unit+                escapeUnits array sourceEnd target (source + 1) (targetIndex + 2)+            else escapeUnits array sourceEnd target (source + 1) (targetIndex + 1)++-- | Escape bytes for a single-quoted literal, see the module header.+escapeBytes :: ByteString -> ByteString+escapeBytes (B.PS fp s len) = unsafeDupablePerformIO $ withForeignPtr fp $ \ a ->+    B.createUptoN (len * 2) $ \ b -> do+        b' <- escapeByteRange (a `plusPtr` s) (a `plusPtr` s `plusPtr` len) b+        return (b' `minusPtr` b)++-- | Copies the bytes from @source@ to @sourceEnd@ to @target@, doubling+-- quotes and backslashes; it returns the address after the last byte written.+escapeByteRange :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO (Ptr Word8)+escapeByteRange !source sourceEnd !target = if source == sourceEnd+    then return target+    else do+        byte <- peek source+        poke target byte+        if byte == quote || byte == backslash+            then do+                pokeByteOff target 1 byte+                escapeByteRange (source `plusPtr` 1) sourceEnd (target `plusPtr` 2)+            else escapeByteRange (source `plusPtr` 1) sourceEnd (target `plusPtr` 1)++quote, backslash :: Num unit => unit+quote = 0x27+backslash = 0x5C
+ src/Database/MySQL/Protocol/FloatingPoint.hs view
@@ -0,0 +1,185 @@+{-|+Module      : Database.MySQL.Protocol.FloatingPoint+Description : FLOAT and DOUBLE text, correctly rounded+Copyright   : (c) Jappie Klooster, 2026+License     : BSD3+Maintainer  : hi@jappie.me+Stability   : experimental+Portability : PORTABLE++The text protocol sends FLOAT and DOUBLE values as decimal text, large ones in+exponent form such as @1e20@. 'readDouble' and 'readFloat' give the nearest+value, as 'read' does, and are fast for the short numbers MySQL usually writes.+See Note [Correctly rounded floating point text].+-}+module Database.MySQL.Protocol.FloatingPoint+  ( readDouble+  , readFloat+  ) where++import           Data.ByteString                (ByteString)+import qualified Data.ByteString                as B+import qualified Data.ByteString.Lex.Fractional as LexFrac+import           Data.Scientific                (Scientific, toRealFloat)+import           Data.Word                      (Word64, Word8)+import qualified Unwitch.Convert.Word64         as Word64+import qualified Unwitch.Convert.Word8          as Word8++{- Note [Correctly rounded floating point text]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+Scaling decimal digits by @10 ^^ e@ in floating point rounds more than once.+bytestring-lexing's readExponential missed the nearest Double for 83% of+20,000 shown doubles, and its readDecimal, which also stops at an @e@ and so+read @1e20@ as 1, missed 2 of 243 plain decimals. Reading the text exactly as a+'Scientific' and rounding once with 'toRealFloat' missed none, but cost about+14,000 instructions per DOUBLE field.++Decision: Clinger's fast path first, then the exact way; the alternatives+above were wrong or slow. The fast path avoids the cost: when the significant+digits, read as an integer, lie in the exact integer range of a Double (below+2^53, where unwitch's 'Word64.toDouble' converts and every integer is exact)+and the power of ten is at most 22 either way, that power is exact as well, so+one multiplication or division rounds once and gives the nearest Double. A+Float takes integers below 2^24 and powers up to 10. A number with more than 19+significant digits does not fit a 'Word64' and has no significand at all, so+it never reaches the fast path. MySQL writes most values within these bounds;+the rest go the exact way.+-}++-- | The nearest 'Double' to the decimal number at the start of the bytes:+-- @-?digits(.digits)?([eE][+-]?digits)?@. Bytes after it are ignored.+readDouble :: ByteString -> Maybe Double+readDouble bytes = do+    decimal <- scanDecimal bytes+    case fastSignificand Word64.toDouble 22 decimal of+        Just exactDigits -> Just (signed decimal (scaleExactly exactDigits (decimalExponent decimal)))+        Nothing          -> exactly bytes++-- | 'readDouble' for 'Float'.+readFloat :: ByteString -> Maybe Float+readFloat bytes = do+    decimal <- scanDecimal bytes+    case fastSignificand Word64.toFloat 10 decimal of+        Just exactDigits -> Just (signed decimal (scaleExactly exactDigits (decimalExponent decimal)))+        Nothing          -> exactly bytes++-- | The significand as an exact @a@, when the fast path applies: it has at+-- most 19 digits, @toExactInteger@ converts it (unwitch's conversions fail+-- outside the exact integer range of the type), and the power of ten is+-- within @maxPower@ either way. 'Nothing' sends the number the exact way, so+-- the conversion's failure is a choice of path, not a lost error.+-- See Note [Correctly rounded floating point text].+fastSignificand :: (Word64 -> Either overflow a) -> Int -> DecimalText -> Maybe a+fastSignificand toExactInteger maxPower decimal = do+    digits <- decimalSignificand decimal+    if abs (decimalExponent decimal) <= maxPower+    then either (const Nothing) Just (toExactInteger digits)+    else Nothing++-- | The significand times @10 ^ powerOfTen@, with one rounding: the caller+-- guarantees that the significand and the power of ten are both exact.+scaleExactly :: RealFloat a => a -> Int -> a+scaleExactly exactDigits powerOfTen =+    if powerOfTen >= 0+    then exactDigits * 10 ^ powerOfTen+    else exactDigits / 10 ^ negate powerOfTen++signed :: Num a => DecimalText -> a -> a+signed decimal = signed' (decimalSign decimal)++signed' :: Num a => Sign -> a -> a+signed' sign magnitude = case sign of+    Positive -> magnitude+    Negative -> negate magnitude++-- | The text read exactly as a 'Scientific', then rounded once.+exactly :: RealFloat a => ByteString -> Maybe a+exactly bytes = toRealFloat <$> exactScientific bytes++exactScientific :: ByteString -> Maybe Scientific+exactScientific bytes = fst <$> LexFrac.readSigned LexFrac.readExponential bytes++data Sign = Positive | Negative++-- | A decimal number in text, as far as the fast path needs it.+data DecimalText = DecimalText+    { decimalSign        :: !Sign+    , decimalSignificand :: !(Maybe Word64)+      -- ^ the digits from the first non-zero one on, without the point, as one+      -- integer; 'Nothing' when there are more than 19, too many for a 'Word64'+    , decimalExponent    :: !Int+      -- ^ the power of ten the significand is scaled by; 'maxBound' when the+      -- written exponent has more digits than the fast path takes+    }++-- | The number at the start of the bytes, or 'Nothing' when they do not+-- start with one.+scanDecimal :: ByteString -> Maybe DecimalText+scanDecimal bytes =+    let (sign, afterSign) = splitSign bytes+        (integerPart, afterInteger) = B.span isDigit afterSign+        (fractionPart, afterFraction) = splitFraction afterInteger+        digits = integerPart <> fractionPart+        significant = B.dropWhile (== 48) digits  -- '0'+    in if B.null integerPart+       then Nothing+       else Just DecimalText+           { decimalSign        = sign+           , decimalSignificand =+               if B.length significant <= 19+               then Just (B.foldl' appendDigit 0 significant)+               else Nothing+           , decimalExponent    = writtenExponent afterFraction `minusDigits` B.length fractionPart+           }++-- | The exponent after @e@ or @E@, 0 when there is none; 'maxBound' when it+-- has more than 6 digits, which no FLOAT or DOUBLE needs.+writtenExponent :: ByteString -> Int+writtenExponent bytes = case B.uncons bytes of+    Just (marker, rest) ->+        if marker == 101 || marker == 69          -- 'e', 'E'+        then exponentDigits rest+        else 0+    Nothing -> 0++exponentDigits :: ByteString -> Int+exponentDigits bytes =+    let (sign, afterSign) = splitSign bytes+        digits = B.takeWhile isDigit afterSign+    in if  | B.null digits       -> 0+           | B.length digits > 6 -> maxBound+           | otherwise           -> signed' sign (B.foldl' appendDigitInt 0 digits)++-- | A leading @-@ or @+@, and the bytes after it.+splitSign :: ByteString -> (Sign, ByteString)+splitSign bytes = case B.uncons bytes of+    Nothing -> (Positive, bytes)+    Just (byte, rest) ->+        if  | byte == 45 -> (Negative, rest)  -- '-'+            | byte == 43 -> (Positive, rest)  -- '+'+            | otherwise  -> (Positive, bytes)++-- | The digits after a decimal point, and the bytes after them; a point+-- without digits after it is not part of the number.+splitFraction :: ByteString -> (ByteString, ByteString)+splitFraction bytes = case B.uncons bytes of+    Nothing -> (B.empty, bytes)+    Just (byte, rest) ->+        let (fraction, remaining) = B.span isDigit rest+        in if byte == 46 && not (B.null fraction)  -- '.'+           then (fraction, remaining)+           else (B.empty, bytes)++-- | Keeps 'maxBound', the mark of an exponent too long to read, as it is.+minusDigits :: Int -> Int -> Int+minusDigits written fractionDigits =+    if written == maxBound then maxBound else written - fractionDigits++isDigit :: Word8 -> Bool+isDigit byte = byte >= 48 && byte <= 57++appendDigit :: Word64 -> Word8 -> Word64+appendDigit accumulated digit = accumulated * 10 + Word8.toWord64 (digit - 48)++appendDigitInt :: Int -> Word8 -> Int+appendDigitInt accumulated digit = accumulated * 10 + Word8.toInt (digit - 48)
+ src/Database/MySQL/Protocol/MySQLValue.hs view
@@ -0,0 +1,1081 @@+{-# OPTIONS_GHC -funbox-strict-fields #-}+{-# LANGUAGE UnboxedSums   #-}+{-# LANGUAGE UnboxedTuples #-}++{-|+Module      : Database.MySQL.Protocol.MySQLValue+Description : Text and binary protocol+Copyright   : (c) Winterland, 2016+License     : BSD+Maintainer  : drkoster@qq.com+Stability   : experimental+Portability : PORTABLE++Core text and binary row decoder/encoder machinery.++-}++module Database.MySQL.Protocol.MySQLValue+  ( -- * MySQLValue decoder and encoder+    MySQLValue(..)+  , RawDate(..)+  , putParamMySQLType+  , getTextField+  , putTextField+  , getTextRow+  , getTextRowVector+    -- * Text rows with the column types resolved once per result set+  , ColumnKind(..)+  , ValueKind(..)+  , columnKind+  , decodeTextRow+  , decodeTextRowVector+  , ColumnNumber(..)+  , nextColumn+  , RowError(..)+  , RowErrorKind(..)+  , TextFieldError(..)+  , InvalidUtf8Text(..)+  , describeRowError+  , getBinaryField+  , putBinaryField+  , getBinaryRow+  , getBinaryRowVector+  , decodeMySQLLatin1+  -- * Internal utilities+  , decodeTextValue+  , decodeTextBit+  , lengthEncodedInt+  , lexSignedIntegral+  , lexSignedFraction+  , textDateParts+  , lexDateTimeParts+  , calendarDate+  , dateValue+  , dateTimeValue+  , timeStampValue+  , lexSignedTime+  , getBits+  , BitMap(..)+  , isColumnSet+  , isColumnNull+  , makeNullMap+  ) where++import qualified Blaze.Text                         as Textual+import           Control.Exception                  (Exception (displayException), throw)+import           Control.Monad+import           Data.Binary.Put+import           Data.Binary.Parser+import           Data.Bits+import           Data.ByteString                    (ByteString)+import qualified Data.ByteString                    as B+import qualified Data.ByteString.Builder            as BB+import           Data.ByteString.Builder.Scientific (FPFormat (..),+                                                     formatScientificBuilder)+import qualified Data.ByteString.Char8              as BC+import qualified Data.ByteString.Lazy               as L+import qualified Data.ByteString.Lex.Fractional     as LexFrac+import qualified Data.ByteString.Lex.Integral       as LexInt+import qualified Data.ByteString.Unsafe             as B+import           Data.Fixed                         (Pico)+import           Data.Int+import           Data.Scientific                    (Scientific)+import           Data.Text                          (Text)+import qualified Data.Text.Encoding                 as T+import           Data.Time.Calendar                 (Day (ModifiedJulianDay),+                                                     fromGregorianValid,+                                                     toGregorian)+import           Data.Time.Format                   (defaultTimeLocale,+                                                     formatTime)+import           Data.Time.LocalTime                (LocalTime (..),+                                                     TimeOfDay (..))+import           Data.Word+import           Database.MySQL.Protocol.ColumnDef+import           Database.MySQL.Protocol.Escape+import           Database.MySQL.Protocol.FloatingPoint (readDouble, readFloat)+import           Database.MySQL.Protocol.Packet+import           GHC.Generics                       (Generic)+import           Text.Printf                        (printf)+import qualified Data.Text                          as T+import qualified Data.Vector                        as V+import qualified Unwitch.Convert.Int                as Int+import qualified Unwitch.Convert.Word16             as Word16+import qualified Unwitch.Convert.Word64             as Word64+import qualified Unwitch.Convert.Word8              as Word8++--------------------------------------------------------------------------------+-- | Data type mapping between MySQL values and haskell values.+--+-- There're some subtle differences between MySQL values and haskell values:+--+-- MySQL's @DATETIME@ and @TIMESTAMP@ are different on timezone handling:+--+--  * DATETIME and DATE is just a represent of a calendar date, it has no timezone information involved,+--  you always get the same value as you put no matter what timezone you're using with MySQL.+--+--  * MySQL converts TIMESTAMP values from the current time zone to UTC for storage,+--  and back from UTC to the current time zone for retrieval. If you put a TIMESTAMP with timezone A,+--  then read it with timezone B, you may get different result because of this conversion, so always+--  be careful about setting up the right timezone with MySQL, you can do it with a simple @SET time_zone = timezone;@+--  for more info on timezone support, please read <http://dev.mysql.com/doc/refman/5.7/en/time-zone-support.html>+--+--  So we use 'LocalTime' to present both @DATETIME@ and @TIMESTAMP@, but the local here is different.+--+-- A @DATE@, @DATETIME@ or @TIMESTAMP@ whose date a 'Day' cannot hold, such as the zero+-- date @0000-00-00@, arrives as 'MySQLDateRaw', 'MySQLDateTimeRaw' or 'MySQLTimeStampRaw'+-- and is sent back unchanged, see 'RawDate'.+--+-- MySQL's @TIME@ type can present time of day, but also elapsed time or a time interval between two events.+-- @TIME@ values may range from @-838:59:59@ to @838:59:59@, so 'MySQLTime' values consist of a sign and a+-- 'TimeOfDay' whose hour part may exceeded 24. you can use @timeOfDayToTime@ to get the absolute time interval.+--+-- Under MySQL >= 5.7, @DATETIME@, @TIMESTAMP@ and @TIME@ may contain fractional part, which matches haskell's+-- precision.+--+data MySQLValue+    = MySQLDecimal       !Scientific   -- ^ DECIMAL, NEWDECIMAL+    | MySQLInt8U         !Word8        -- ^ Unsigned TINY+    | MySQLInt8          !Int8         -- ^ TINY+    | MySQLInt16U        !Word16       -- ^ Unsigned SHORT+    | MySQLInt16         !Int16        -- ^ SHORT+    | MySQLInt32U        !Word32       -- ^ Unsigned LONG, INT24+    | MySQLInt32         !Int32        -- ^ LONG, INT24+    | MySQLInt64U        !Word64       -- ^ Unsigned LONGLONG+    | MySQLInt64         !Int64        -- ^ LONGLONG+    | MySQLFloat         !Float        -- ^ IEEE 754 single precision format+    | MySQLDouble        !Double       -- ^ IEEE 754 double precision format+    | MySQLYear          !Word16       -- ^ YEAR+    | MySQLDateTime      !LocalTime    -- ^ DATETIME+    | MySQLTimeStamp     !LocalTime    -- ^ TIMESTAMP+    | MySQLDate          !Day              -- ^ DATE+    | MySQLDateRaw       !RawDate              -- ^ DATE that a 'Day' cannot hold+    | MySQLDateTimeRaw   !RawDate !TimeOfDay   -- ^ DATETIME whose date a 'Day' cannot hold+    | MySQLTimeStampRaw  !RawDate !TimeOfDay   -- ^ TIMESTAMP whose date a 'Day' cannot hold+    | MySQLTime          !Word8 !TimeOfDay -- ^ sign(0 = non-negative, 1 = negative) hh mm ss microsecond+                                           -- The sign is OPPOSITE to binlog one !!!+    | MySQLGeometry      !ByteString       -- ^ todo: parsing to something meanful+    | MySQLBytes         !ByteString+    | MySQLBit           !Word64+    | MySQLText          !Text+    | MySQLNull+  deriving (Show, Eq, Generic)++-- | A date as the server stores it, when a 'Day' cannot hold it: the zero date+-- @0000-00-00@, a date with a zero month or day such as @2024-05-00@ (unless+-- @NO_ZERO_DATE@ and @NO_ZERO_IN_DATE@ are set), or under @ALLOW_INVALID_DATES@+-- a day past the end of its month such as @2024-02-30@.+data RawDate = RawDate+    { rawDateYear  :: !Word16+    , rawDateMonth :: !Word8+    , rawDateDay   :: !Word8+    } deriving (Show, Eq, Generic)++-- | 'Right' when a 'Day' can hold the date.+calendarDate :: RawDate -> Either RawDate Day+calendarDate raw@(RawDate year month day) =+    -- Every month has 28 days, so these dates exist in every year and need no+    -- check; only the rest go through 'fromGregorianValid', whose Integer+    -- arithmetic took about a quarter of the instructions on date-heavy rows+    -- (docs/direct-row-decoding.md, 1.4.1).+    if month >= 1 && month <= 12 && day >= 1 && day <= 28+    then Right (dayOfCommonDate (Word16.toInt year) (Word8.toInt month) (Word8.toInt day))+    else maybe (Left raw) Right+        (fromGregorianValid (Word16.toInteger year) (Word8.toInt month) (Word8.toInt day))++-- | The 'Day' of a date that exists, computed in 'Int': Howard Hinnant's+-- days_from_civil (http://howardhinnant.github.io/date_algorithms.html),+-- counted from the Modified Julian Day epoch, 1858-11-17. The year counts from+-- March, so that February's leap day falls at its end.+dayOfCommonDate :: Int -> Int -> Int -> Day+dayOfCommonDate year month day =+    let marchYear = if month <= 2 then year - 1 else year+        era = marchYear `div` 400+        yearOfEra = marchYear - era * 400+        dayOfEra = yearOfEra * 365 + yearOfEra `div` 4 - yearOfEra `div` 100 + marchDayOfYear month day+    in ModifiedJulianDay (Int.toInteger (era * 146097 + dayOfEra - 678881))++-- | The day's number in a year that starts on 1 March, from 0.+marchDayOfYear :: Int -> Int -> Int+marchDayOfYear month day = (153 * (if month > 2 then month - 3 else month + 9) + 2) `div` 5 + day - 1++dateValue :: RawDate -> MySQLValue+dateValue = either MySQLDateRaw MySQLDate . calendarDate++dateTimeValue :: RawDate -> TimeOfDay -> MySQLValue+dateTimeValue raw time =+    either (`MySQLDateTimeRaw` time) (\day -> MySQLDateTime (LocalTime day time)) (calendarDate raw)++timeStampValue :: RawDate -> TimeOfDay -> MySQLValue+timeStampValue raw time =+    either (`MySQLTimeStampRaw` time) (\day -> MySQLTimeStamp (LocalTime day time)) (calendarDate raw)++-- | Put 'FieldType' and usigned bit(0x80/0x00) for 'MySQLValue's.+--+putParamMySQLType :: MySQLValue -> Put+putParamMySQLType (MySQLDecimal      _)  = putFieldType mySQLTypeDecimal  >> putWord8 0x00+putParamMySQLType (MySQLInt8U        _)  = putFieldType mySQLTypeTiny     >> putWord8 0x80+putParamMySQLType (MySQLInt8         _)  = putFieldType mySQLTypeTiny     >> putWord8 0x00+putParamMySQLType (MySQLInt16U       _)  = putFieldType mySQLTypeShort    >> putWord8 0x80+putParamMySQLType (MySQLInt16        _)  = putFieldType mySQLTypeShort    >> putWord8 0x00+putParamMySQLType (MySQLInt32U       _)  = putFieldType mySQLTypeLong     >> putWord8 0x80+putParamMySQLType (MySQLInt32        _)  = putFieldType mySQLTypeLong     >> putWord8 0x00+putParamMySQLType (MySQLInt64U       _)  = putFieldType mySQLTypeLongLong >> putWord8 0x80+putParamMySQLType (MySQLInt64        _)  = putFieldType mySQLTypeLongLong >> putWord8 0x00+putParamMySQLType (MySQLFloat        _)  = putFieldType mySQLTypeFloat    >> putWord8 0x00+putParamMySQLType (MySQLDouble       _)  = putFieldType mySQLTypeDouble   >> putWord8 0x00+putParamMySQLType (MySQLYear         _)  = putFieldType mySQLTypeShort    >> putWord8 0x80+putParamMySQLType (MySQLDateTime     _)  = putFieldType mySQLTypeDateTime >> putWord8 0x00+putParamMySQLType (MySQLTimeStamp    _)  = putFieldType mySQLTypeTimestamp>> putWord8 0x00+putParamMySQLType (MySQLDate         _)  = putFieldType mySQLTypeDate     >> putWord8 0x00+putParamMySQLType (MySQLDateRaw      _)  = putFieldType mySQLTypeDate     >> putWord8 0x00+putParamMySQLType (MySQLDateTimeRaw _ _) = putFieldType mySQLTypeDateTime >> putWord8 0x00+putParamMySQLType (MySQLTimeStampRaw _ _) = putFieldType mySQLTypeTimestamp >> putWord8 0x00+putParamMySQLType (MySQLTime       _ _)  = putFieldType mySQLTypeTime     >> putWord8 0x00+putParamMySQLType (MySQLBytes        _)  = putFieldType mySQLTypeBlob     >> putWord8 0x00+putParamMySQLType (MySQLGeometry     _)  = putFieldType mySQLTypeGeometry >> putWord8 0x00+putParamMySQLType (MySQLBit          _)  = putFieldType mySQLTypeLongLong >> putWord8 0x80+putParamMySQLType (MySQLText         _)  = putFieldType mySQLTypeString   >> putWord8 0x00+putParamMySQLType MySQLNull              = putFieldType mySQLTypeNull     >> putWord8 0x00++--------------------------------------------------------------------------------+-- | Text protocol decoder+getTextField :: ColumnDef -> Get MySQLValue+getTextField f = case columnKind f of+    NullColumn -> pure MySQLNull+    ValueColumn fieldType value -> do+        bytes <- getLenEncBytes+        case decodeTextValue fieldType value bytes of+            Left fieldError -> fail (describeTextFieldError fieldError)+            Right decoded   -> pure decoded++{- Note [Text rows decoded once per result set]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+'getTextRow' runs 'getTextField' for every field of every row, which compares+the column type against two dozen constants before it reads a byte, inside+binary's continuation-based 'Get' over a lazy packet body. Decoding text rows+was 47.5% of select CPU (benchmark/cpu-profile-1.3.3.md).++The query functions in "Database.MySQL.Base" instead resolve each column to a+'ColumnKind' once, when the column definitions arrive, and decode every row+with 'decodeTextRow': a walk over the row as one strict 'ByteString' that+returns unboxed sums, so no 'Either' or tuple is allocated per field.++Decision: values stay as lazy as 'getTextField' leaves them. Evaluating every+value while decoding saved 3% of the instructions when a caller uses every+value, and cost 2.75 times the instructions when it uses none+(docs/direct-row-decoding.md). A string is decoded as UTF-8 only when its+value is used, as in 1.3: bytes that are not UTF-8 raise 'InvalidUtf8Text'+from pure code then. 1.4.0 checked every string while reading the row+instead, which cost a validation pass on text nobody looked at and a second+pass on text that was used; callers who want the check up front use the+typed decoders of "Database.MySQL.Decoder", which check as they read.++'getTextField' and 'getTextRow' keep their 'Get' interface and share+'columnKind' and 'decodeTextValue', so both paths turn the same bytes into the+same values. They differ only on rows a server does not send: an 8-byte length+above 'maxBound' of 'Int', which 'decodeTextRow' rejects, and a zero-length+BIT field, which is now 0 instead of the byte after it.+-}++-- | How a column's fields decode, in either protocol, resolved once per result+-- set by 'columnKind'. See Note [Text rows decoded once per result set].+--+-- @since 1.4.0+data ColumnKind+    = NullColumn+      -- ^ a column of type NULL: every field of it is NULL, so no bytes of a+      -- text-protocol row belong to it beyond the NULL marker+    | ValueColumn !FieldType !ValueKind+      -- ^ a column with values; the column type is kept for error messages+    deriving (Show, Eq)++-- | The 'MySQLValue' constructor a column's non-NULL fields become.+--+-- @since 1.4.0+data ValueKind+    = KindDecimal+    | KindInt8U+    | KindInt8+    | KindInt16U+    | KindInt16+    | KindInt32U+    | KindInt32+    | KindInt64U+    | KindInt64+    | KindFloat+    | KindDouble+    | KindYear+    | KindTimeStamp+    | KindDateTime+    | KindDate+    | KindTime+    | KindGeometry+    | KindText !Word16+      -- ^ a string column in a character set other than binary or latin1, read+      -- as UTF-8; the character set id is kept for error messages+    | KindLatin1Text   -- ^ a string column in latin1, which MySQL means as Windows-1252+    | KindBytes        -- ^ a string column in the binary character set+    | KindBit+    | KindUnsupported  -- ^ a column type without a decoder+    deriving (Show, Eq)++-- | How a column's fields decode.+--+-- @since 1.4.0+columnKind :: ColumnDef -> ColumnKind+columnKind f =+    if  | t == mySQLTypeNull -> NullColumn+        | t == mySQLTypeDecimal || t == mySQLTypeNewDecimal -> ValueColumn t KindDecimal+        | t == mySQLTypeTiny ->+            ValueColumn t (if isUnsigned then KindInt8U else KindInt8)+        | t == mySQLTypeShort ->+            ValueColumn t (if isUnsigned then KindInt16U else KindInt16)+        | t == mySQLTypeLong || t == mySQLTypeInt24 ->+            ValueColumn t (if isUnsigned then KindInt32U else KindInt32)+        | t == mySQLTypeLongLong ->+            ValueColumn t (if isUnsigned then KindInt64U else KindInt64)+        | t == mySQLTypeFloat -> ValueColumn t KindFloat+        | t == mySQLTypeDouble -> ValueColumn t KindDouble+        | t == mySQLTypeYear -> ValueColumn t KindYear+        | t == mySQLTypeTimestamp || t == mySQLTypeTimestamp2 -> ValueColumn t KindTimeStamp+        | t == mySQLTypeDateTime || t == mySQLTypeDateTime2 -> ValueColumn t KindDateTime+        | t == mySQLTypeDate || t == mySQLTypeNewDate -> ValueColumn t KindDate+        | t == mySQLTypeTime || t == mySQLTypeTime2 -> ValueColumn t KindTime+        | t == mySQLTypeGeometry -> ValueColumn t KindGeometry+        | t == mySQLTypeVarChar+            || t == mySQLTypeEnum+            || t == mySQLTypeSet+            || t == mySQLTypeTinyBlob+            || t == mySQLTypeMediumBlob+            || t == mySQLTypeLongBlob+            || t == mySQLTypeBlob+            || t == mySQLTypeVarString+            || t == mySQLTypeString ->+            ValueColumn t (stringKind (columnCharSet f))+        | t == mySQLTypeBit -> ValueColumn t KindBit+        | otherwise -> ValueColumn t KindUnsupported+  where+    t = columnType f+    isUnsigned = flagUnsigned (columnFlags f)++-- | Why the bytes of a text-protocol field did not decode.+--+-- @since 1.4.0+data TextFieldError+    = TextFieldUnparsable !FieldType !ByteString+      -- ^ the bytes are not a value of the column's type+    | TextFieldBitTooWide !Int+      -- ^ a BIT field longer than the 8 bytes of a 'Word64'+    | TextFieldUnsupportedType !FieldType+      -- ^ a column type without a text decoder+    | TextFieldInvalidUtf8 !Word16 !ByteString+      -- ^ a string in this character set id whose bytes are not UTF-8. Not+      -- raised since 1.4.1, where such a string raises 'InvalidUtf8Text' when+      -- its value is used; kept so that matches on it still compile.+    deriving (Show, Eq)++-- | A string value, in the character set with this id, whose bytes are not+-- UTF-8. Raised when the 'MySQLText' is evaluated, not while its row is read.+--+-- @since 1.4.1+data InvalidUtf8Text = InvalidUtf8Text !Word16 !ByteString+    deriving (Show, Eq)++instance Exception InvalidUtf8Text where+    displayException (InvalidUtf8Text charsetId _) =+        "Database.MySQL.Protocol.MySQLValue: a string value arrived in character set id "+            ++ show charsetId ++ ", which mysql-haskell can only decode as UTF-8 or latin1,"+            ++ " and its bytes are not valid UTF-8. Use a UTF-8 connection charset such as"+            ++ " defaultConnectInfoMB4's, or select the column as BINARY to get its bytes."++-- | The message 'getTextField' fails with.+describeTextFieldError :: TextFieldError -> String+describeTextFieldError fieldError = case fieldError of+    TextFieldUnparsable fieldType bytes ->+        "Database.MySQL.Protocol.MySQLValue: parsing " ++ show fieldType+            ++ " failed, input: " ++ BC.unpack bytes+    TextFieldBitTooWide width ->+        "Database.MySQL.Protocol.MySQLValue: wrong bit length size: " ++ show width+    TextFieldUnsupportedType fieldType ->+        "Database.MySQL.Protocol.MySQLValue: missing text decoder for " ++ show fieldType+    TextFieldInvalidUtf8 charsetId _ ->+        "Database.MySQL.Protocol.MySQLValue: a string column arrived in character set id "+            ++ show charsetId ++ ", which mysql-haskell can only decode as UTF-8 or latin1,"+            ++ " and its bytes are not valid UTF-8. Use a UTF-8 connection charset such as"+            ++ " defaultConnectInfoMB4's, or select the column as BINARY to get its bytes."++-- | The value of a field's bytes. The lexer runs at once, so a malformed field+-- fails while the row is read; a lexed value and the 'Text' of a string are left lazy,+-- while a constructor around bytes already in hand is built at once, as that+-- is cheaper than a thunk. See Note [Text rows decoded once per result set].+-- Inlined so that 'decodeTextFieldAt' matches the 'Right' away instead of+-- allocating it.+decodeTextValue :: FieldType -> ValueKind -> ByteString -> Either TextFieldError MySQLValue+decodeTextValue fieldType value bytes = case value of+    KindDecimal     -> lexedValue fieldType MySQLDecimal lexSignedFraction bytes+    KindInt8U       -> lexedValue fieldType MySQLInt8U lexSignedIntegral bytes+    KindInt8        -> lexedValue fieldType MySQLInt8 lexSignedIntegral bytes+    KindInt16U      -> lexedValue fieldType MySQLInt16U lexSignedIntegral bytes+    KindInt16       -> lexedValue fieldType MySQLInt16 lexSignedIntegral bytes+    KindInt32U      -> lexedValue fieldType MySQLInt32U lexSignedIntegral bytes+    KindInt32       -> lexedValue fieldType MySQLInt32 lexSignedIntegral bytes+    KindInt64U      -> lexedValue fieldType MySQLInt64U lexSignedIntegral bytes+    KindInt64       -> lexedValue fieldType MySQLInt64 lexSignedIntegral bytes+    KindFloat       -> lexedValue fieldType MySQLFloat readFloat bytes+    KindDouble      -> lexedValue fieldType MySQLDouble readDouble bytes+    KindYear        -> lexedValue fieldType MySQLYear lexSignedIntegral bytes+    KindTimeStamp   -> lexedValue fieldType (uncurry timeStampValue) lexDateTimeParts bytes+    KindDateTime    -> lexedValue fieldType (uncurry dateTimeValue) lexDateTimeParts bytes+    KindDate        -> lexedValue fieldType dateValue textDateParts bytes+    KindTime        -> lexedValue fieldType id lexTime bytes+    KindGeometry    -> Right $! MySQLGeometry bytes+    KindText charsetId -> utf8Value charsetId bytes+    KindLatin1Text  -> Right (MySQLText (decodeMySQLLatin1 bytes))+    KindBytes       -> Right $! MySQLBytes bytes+    KindBit         -> decodeTextBit bytes+    KindUnsupported -> Left (TextFieldUnsupportedType fieldType)+{-# INLINE decodeTextValue #-}++-- | Always 'Right': the 'MySQLText' is decoded, in one UTF-8 pass, when it is+-- used. See Note [Text rows decoded once per result set].+utf8Value :: Word16 -> ByteString -> Either TextFieldError MySQLValue+utf8Value charsetId bytes = Right (lazyUtf8Text charsetId bytes)+{-# INLINE utf8Value #-}++lazyUtf8Text :: Word16 -> ByteString -> MySQLValue+lazyUtf8Text charsetId bytes = case T.decodeUtf8' bytes of+    Right decoded -> MySQLText decoded+    Left _        -> throw (InvalidUtf8Text charsetId bytes)++lexedValue :: FieldType -> (a -> MySQLValue) -> (ByteString -> Maybe a) -> ByteString+           -> Either TextFieldError MySQLValue+lexedValue fieldType construct lexer bytes = case lexer bytes of+    Just lexed -> Right (construct lexed)+    Nothing    -> Left (TextFieldUnparsable fieldType bytes)+{-# INLINE lexedValue #-}++-- | A BIT field's bytes, most significant first.+decodeTextBit :: ByteString -> Either TextFieldError MySQLValue+decodeTextBit bytes =+    if B.length bytes > 8+    then Left (TextFieldBitTooWide (B.length bytes))+    else Right $! MySQLBit (B.foldl' appendBitByte 0 bytes)++appendBitByte :: Word64 -> Word8 -> Word64+appendBitByte bits byte = unsafeShiftL bits 8 .|. Word8.toWord64 byte++lexSignedIntegral :: Integral a => ByteString -> Maybe a+lexSignedIntegral bytes = fst <$> LexInt.readSigned LexInt.readDecimal bytes+-- Specialised here so that "Database.MySQL.Decoder", which reaches these+-- lexers through higher-order arguments, gets the same specialised digit loops+-- as 'decodeTextValue' instead of the dictionary-passing ones.+{-# SPECIALIZE lexSignedIntegral :: ByteString -> Maybe Int8 #-}+{-# SPECIALIZE lexSignedIntegral :: ByteString -> Maybe Word8 #-}+{-# SPECIALIZE lexSignedIntegral :: ByteString -> Maybe Int16 #-}+{-# SPECIALIZE lexSignedIntegral :: ByteString -> Maybe Word16 #-}+{-# SPECIALIZE lexSignedIntegral :: ByteString -> Maybe Int32 #-}+{-# SPECIALIZE lexSignedIntegral :: ByteString -> Maybe Word32 #-}+{-# SPECIALIZE lexSignedIntegral :: ByteString -> Maybe Int64 #-}+{-# SPECIALIZE lexSignedIntegral :: ByteString -> Maybe Word64 #-}++lexSignedFraction :: Fractional a => ByteString -> Maybe a+lexSignedFraction bytes = fst <$> LexFrac.readSigned LexFrac.readDecimal bytes+{-# SPECIALIZE lexSignedFraction :: ByteString -> Maybe Scientific #-}+++-- | @YYYY-MM-DD hh:mm:ss[.fraction]@, as DATETIME and TIMESTAMP fields come;+-- the date may be one a 'Day' cannot hold.+lexDateTimeParts :: ByteString -> Maybe (RawDate, TimeOfDay)+lexDateTimeParts bytes = do+    guard (B.length bytes >= 12)+    (,) <$> textDateParts bytes <*> lexTimeOfDay (B.drop 11 bytes)++lexTime :: ByteString -> Maybe MySQLValue+lexTime bytes = uncurry MySQLTime <$> lexSignedTime bytes++-- | A TIME field: an optional minus sign before @hh:mm:ss[.fraction]@, where the+-- hours may exceed 24. The sign is 1 for negative, as in 'MySQLTime'.+lexSignedTime :: ByteString -> Maybe (Word8, TimeOfDay)+lexSignedTime bytes = do+    guard (not (B.null bytes))+    if B.index bytes 0 == 45  -- '-'+    then (,) 1 <$> lexTimeOfDay (B.drop 1 bytes)+    else (,) 0 <$> lexTimeOfDay bytes++-- | @hh:mm:ss[.fraction]@.+lexTimeOfDay :: ByteString -> Maybe TimeOfDay+lexTimeOfDay bytes = do+    (hh, rest) <- LexInt.readDecimal bytes+    guard (not (B.null rest))+    (mm, rest') <- LexInt.readDecimal (B.tail rest)+    guard (not (B.null rest'))+    (ss, _) <- LexFrac.readDecimal (B.tail rest')+    return (TimeOfDay hh mm ss)++-- | A column's position in a result set, counting from 0.+--+-- @since 1.4.0+newtype ColumnNumber = ColumnNumber Int+    deriving (Show, Eq, Ord)++-- | @since 1.4.0+nextColumn :: ColumnNumber -> ColumnNumber+nextColumn (ColumnNumber column) = ColumnNumber (column + 1)++-- | Where and why a text-protocol row did not decode.+--+-- @since 1.4.0+data RowError = RowError+    { rowErrorColumn :: !ColumnNumber+    , rowErrorOffset :: !Int               -- ^ byte offset of that column's field in the row+    , rowErrorKind   :: !RowErrorKind+    } deriving (Show, Eq)++-- | @since 1.4.0+data RowErrorKind+    = RowEndsEarly+      -- ^ the row ends before this column's field does+    | RowInvalidLengthPrefix !Word8+      -- ^ a field starting with a byte that is neither a length nor the NULL marker+    | RowLengthOverflow+      -- ^ an 8-byte length above 'maxBound' of 'Int'+    | RowFieldError !TextFieldError+      -- ^ the field's bytes did not decode+    deriving (Show, Eq)++-- | @since 1.4.0+describeRowError :: RowError -> String+describeRowError (RowError (ColumnNumber column) offset kind) =+    "Database.MySQL.Protocol.MySQLValue: column " ++ show column ++ " at byte "+        ++ show offset ++ ": " ++ kindDescription+  where+    kindDescription = case kind of+        RowEndsEarly                  -> "the row ends inside this field"+        RowInvalidLengthPrefix prefix -> "invalid length prefix " ++ show prefix+        RowLengthOverflow             -> "length does not fit an Int"+        RowFieldError fieldError      -> describeTextFieldError fieldError++-- | The values of a text-protocol row, one per 'ColumnKind'. Bytes after the+-- last column are ignored, as 'getTextRow' ignores them.+-- See Note [Text rows decoded once per result set].+--+-- @since 1.4.0+decodeTextRow :: [ColumnKind] -> ByteString -> Either RowError [MySQLValue]+decodeTextRow columns row = case decodeTextFields row (ColumnNumber 0) 0 columns of+    (# rowError | #) -> Left rowError+    (# | values #)   -> Right values++-- | 'V.Vector' version of 'decodeTextRow'.+--+-- @since 1.4.0+decodeTextRowVector :: V.Vector ColumnKind -> ByteString -> Either RowError (V.Vector MySQLValue)+decodeTextRowVector columns row =+    V.fromListN (V.length columns) <$> decodeTextRow (V.toList columns) row++-- | The fields of @columns@, the first of which is @column@ and starts at byte+-- @offset@ of the row.+decodeTextFields :: ByteString -> ColumnNumber -> Int -> [ColumnKind] -> (# RowError | [MySQLValue] #)+decodeTextFields row columnNumber offset columns = case columns of+    [] -> (# | [] #)+    column : laterColumns -> case decodeTextFieldAt row columnNumber offset column of+        (# rowError | #) -> (# rowError | #)+        (# | (# value, nextOffset #) #) ->+            case decodeTextFields row (nextColumn columnNumber) nextOffset laterColumns of+                (# rowError | #) -> (# rowError | #)+                (# | values #)   -> (# | value : values #)++-- | The field at byte @offset@ and the offset after it. The NULL marker is+-- checked before the column, as 'getTextRow' does.+decodeTextFieldAt :: ByteString -> ColumnNumber -> Int -> ColumnKind+                  -> (# RowError | (# MySQLValue, Int #) #)+decodeTextFieldAt row columnNumber offset column =+    if  | offset >= B.length row ->+            (# RowError columnNumber offset RowEndsEarly | #)+        | B.unsafeIndex row offset == 0xFB -> (# | (# MySQLNull, offset + 1 #) #)+        | otherwise -> case column of+            NullColumn -> (# | (# MySQLNull, offset #) #)+            ValueColumn fieldType value -> case lengthEncodedInt row offset of+                (# kind | #) -> (# RowError columnNumber offset kind | #)+                (# | (# fieldLength, fieldStart #) #) ->+                    if fieldLength > B.length row - fieldStart+                    then (# RowError columnNumber offset RowEndsEarly | #)+                    else case decodeTextValue fieldType value+                                (B.unsafeTake fieldLength (B.unsafeDrop fieldStart row)) of+                        Left fieldError ->+                            (# RowError columnNumber offset (RowFieldError fieldError) | #)+                        Right decoded -> (# | (# decoded, fieldStart + fieldLength #) #)++-- | The length-encoded integer at byte @offset@, which must be inside the row,+-- and the offset after it.+lengthEncodedInt :: ByteString -> Int -> (# RowErrorKind | (# Int, Int #) #)+lengthEncodedInt row offset =+    let prefix = B.unsafeIndex row offset+    in if  | prefix < 0xFB  -> (# | (# Word8.toInt prefix, offset + 1 #) #)+           | prefix == 0xFC -> littleEndianLength row (offset + 1) 2+           | prefix == 0xFD -> littleEndianLength row (offset + 1) 3+           | prefix == 0xFE -> littleEndianLength row (offset + 1) 8+           | otherwise      -> (# RowInvalidLengthPrefix prefix | #)++-- | The little-endian length of @width@ bytes at byte @start@, and the offset after it.+littleEndianLength :: ByteString -> Int -> Int -> (# RowErrorKind | (# Int, Int #) #)+littleEndianLength row start width =+    if width > B.length row - start+    then (# RowEndsEarly | #)+    else case Word64.toInt (B.foldr' prependLittleEndianByte 0+                                (B.unsafeTake width (B.unsafeDrop start row))) of+        Nothing          -> (# RowLengthOverflow | #)+        Just fieldLength -> (# | (# fieldLength, start + width #) #)++prependLittleEndianByte :: Word8 -> Word64 -> Word64+prependLittleEndianByte byte higherBytes = unsafeShiftL higherBytes 8 .|. Word8.toWord64 byte++-- | The @YYYY-MM-DD@ that starts a text-protocol date, which may be zero.+textDateParts :: ByteString -> Maybe RawDate+textDateParts bs = do+    (yyyy, rest) <- LexInt.readDecimal bs :: Maybe (Int, ByteString)+    guard (not (B.null rest))+    (mm, rest') <- LexInt.readDecimal (B.tail rest) :: Maybe (Int, ByteString)+    guard (not (B.null rest'))+    (dd, _) <- LexInt.readDecimal (B.tail rest') :: Maybe (Int, ByteString)+    RawDate <$> Int.toWord16 yyyy <*> Int.toWord8 mm <*> Int.toWord8 dd++-- | How the bytes of a string column decode, by the character set id the+-- server sent them in. See Note [Result character sets].+stringKind :: Word16 -> ValueKind+stringKind charsetId =+    if  | charsetId == 63 -> KindBytes+        | charsetId `elem` latin1Collations -> KindLatin1Text+        | otherwise -> KindText charsetId++-- | The ids of @information_schema.collations@ whose character set is latin1,+-- in MySQL 8.0 and MariaDB 11.4 (which adds the two nopad ones).+latin1Collations :: [Word16]+latin1Collations = [5, 8, 15, 31, 47, 48, 49, 94, 1032, 1071]++-- Note [Result character sets]+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~+-- The server converts string columns to character_set_results, so with a+-- UTF-8 connection charset every text column arrives as UTF-8. A connection+-- set to another charset, as after @SET NAMES latin1@, gets that charset's+-- bytes. latin1 is decoded here; any other charset is read as UTF-8, which+-- keeps ASCII-only values working as they always did. Bytes that are not+-- UTF-8 raise an 'InvalidUtf8Text' naming the charset when the value is used,+-- rather than return mangled text.++stringColumnValue :: ColumnDef -> ByteString -> Get MySQLValue+stringColumnValue column bytes =+    case decodeTextValue (columnType column) (stringKind (columnCharSet column)) bytes of+        Right value -> pure value+        Left fieldError ->+            fail (describeTextFieldError fieldError ++ " Column: " ++ show (columnName column))++-- | MySQL's latin1, which is Windows-1252 with its five unassigned bytes+-- (0x81, 0x8D, 0x8F, 0x90, 0x9D) mapped to the C1 controls of the same number.+-- 'T.decodeLatin1' alone is ISO-8859-1, which reads all of 0x80 to 0x9F as+-- C1 controls, so the 27 bytes Windows-1252 assigns are remapped after it.+decodeMySQLLatin1 :: ByteString -> Text+decodeMySQLLatin1 = T.map windows1252Char . T.decodeLatin1++windows1252Char :: Char -> Char+windows1252Char char = case char of+    '\x80' -> '\x20AC'+    '\x82' -> '\x201A'+    '\x83' -> '\x0192'+    '\x84' -> '\x201E'+    '\x85' -> '\x2026'+    '\x86' -> '\x2020'+    '\x87' -> '\x2021'+    '\x88' -> '\x02C6'+    '\x89' -> '\x2030'+    '\x8A' -> '\x0160'+    '\x8B' -> '\x2039'+    '\x8C' -> '\x0152'+    '\x8E' -> '\x017D'+    '\x91' -> '\x2018'+    '\x92' -> '\x2019'+    '\x93' -> '\x201C'+    '\x94' -> '\x201D'+    '\x95' -> '\x2022'+    '\x96' -> '\x2013'+    '\x97' -> '\x2014'+    '\x98' -> '\x02DC'+    '\x99' -> '\x2122'+    '\x9A' -> '\x0161'+    '\x9B' -> '\x203A'+    '\x9C' -> '\x0153'+    '\x9E' -> '\x017E'+    '\x9F' -> '\x0178'+    sameCodePoint -> sameCodePoint++feedLenEncBytes :: FieldType -> (t -> b) -> (ByteString -> Maybe t) -> Get b+feedLenEncBytes typ con parser = do+    bs <- getLenEncBytes+    case parser bs of+        Just v -> return (con v)+        Nothing -> fail $ "Database.MySQL.Protocol.MySQLValue: parsing " ++ show typ ++ " failed, \+                          \input: " ++ BC.unpack bs+{-# INLINE feedLenEncBytes #-}++--------------------------------------------------------------------------------+-- | Text protocol encoder+putTextField :: MySQLValue -> Put+putTextField (MySQLDecimal    n) = putBuilder (formatScientificBuilder Fixed Nothing n)+putTextField (MySQLInt8U      n) = putBuilder (Textual.integral n)+putTextField (MySQLInt8       n) = putBuilder (Textual.integral n)+putTextField (MySQLInt16U     n) = putBuilder (Textual.integral n)+putTextField (MySQLInt16      n) = putBuilder (Textual.integral n)+putTextField (MySQLInt32U     n) = putBuilder (Textual.integral n)+putTextField (MySQLInt32      n) = putBuilder (Textual.integral n)+putTextField (MySQLInt64U     n) = putBuilder (Textual.integral n)+putTextField (MySQLInt64      n) = putBuilder (Textual.integral n)+putTextField (MySQLFloat      x) = putBuilder (Textual.float x)+putTextField (MySQLDouble     x) = putBuilder (Textual.double x)+putTextField (MySQLYear       n) = putBuilder (Textual.integral n)+putTextField (MySQLDateTime  dt) = putInQuotes $+                                      putByteString (BC.pack (formatTime defaultTimeLocale "%F %T%Q" dt))+putTextField (MySQLTimeStamp dt) = putInQuotes $+                                      putByteString (BC.pack (formatTime defaultTimeLocale "%F %T%Q" dt))+putTextField (MySQLDate       d) = putInQuotes $+                                      putByteString (BC.pack (formatTime defaultTimeLocale "%F" d))+putTextField (MySQLDateRaw    d) = putInQuotes $ putByteString (BC.pack (rawDateText d))+putTextField (MySQLDateTimeRaw d t) = putInQuotes $+                                      putByteString (BC.pack (rawDateText d ++ formatTime defaultTimeLocale " %T%Q" t))+putTextField (MySQLTimeStampRaw d t) = putInQuotes $+                                      putByteString (BC.pack (rawDateText d ++ formatTime defaultTimeLocale " %T%Q" t))+putTextField (MySQLTime  sign t) = putInQuotes $ do+                                      when (sign == 1) (putCharUtf8 '-')+                                      putByteString (BC.pack (formatTime defaultTimeLocale "%T%Q" t))+                                      -- this works even for hour > 24+putTextField (MySQLGeometry  bs) = putInQuotes $ putByteString . escapeBytes $ bs+putTextField (MySQLBytes     bs) = putInQuotes $ putByteString . escapeBytes $ bs+putTextField (MySQLText       t) = putInQuotes $+                                      putByteString . T.encodeUtf8 . escapeText $ t+putTextField (MySQLBit        b) = do putBuilder "b\'"+                                      putBuilder . execPut $ putTextBits b+                                      putCharUtf8 '\''+  where+    putTextBits :: Word64 -> Put+    putTextBits word = forM_ [63,62..0] $ \ pos ->+            if word `testBit` pos then putCharUtf8 '1' else putCharUtf8 '0'+    {-# INLINE putTextBits #-}++putTextField MySQLNull           = putBuilder "NULL"++rawDateText :: RawDate -> String+rawDateText (RawDate year month day) = printf "%04d-%02d-%02d" year month day++putInQuotes :: Put -> Put+putInQuotes p = putCharUtf8 '\'' >> p >> putCharUtf8 '\''+{-# INLINE putInQuotes #-}++--------------------------------------------------------------------------------+-- | Text row decoder+getTextRow :: [ColumnDef] -> Get [MySQLValue]+getTextRow fs = forM fs $ \ f -> do+    p <- peek+    if p == 0xFB+    then skipN 1 >> return MySQLNull+    else getTextField f+{-# INLINE getTextRow #-}++getTextRowVector :: V.Vector ColumnDef -> Get (V.Vector MySQLValue)+getTextRowVector fs = V.forM fs $ \ f -> do+    p <- peek+    if p == 0xFB+    then skipN 1 >> return MySQLNull+    else getTextField f+{-# INLINE getTextRowVector #-}++--------------------------------------------------------------------------------+-- | Binary protocol decoder+getBinaryField :: ColumnDef -> Get MySQLValue+getBinaryField f+    | t == mySQLTypeNull              = pure MySQLNull+    | t == mySQLTypeDecimal+        || t == mySQLTypeNewDecimal   = feedLenEncBytes t MySQLDecimal fracLexer+    | t == mySQLTypeTiny              = if isUnsigned then MySQLInt8U <$> getWord8+                                                      else MySQLInt8  <$> getInt8+    | t == mySQLTypeShort             = if isUnsigned then MySQLInt16U <$> getWord16le+                                                      else MySQLInt16  <$> getInt16le+    | t == mySQLTypeLong+        || t == mySQLTypeInt24        = if isUnsigned then MySQLInt32U <$> getWord32le+                                                      else MySQLInt32  <$> getInt32le+    | t == mySQLTypeYear              = MySQLYear <$> getWord16le+    | t == mySQLTypeLongLong          = if isUnsigned then MySQLInt64U <$> getWord64le+                                                      else MySQLInt64  <$> getInt64le+    | t == mySQLTypeFloat             = MySQLFloat  <$> getFloatle+    | t == mySQLTypeDouble            = MySQLDouble <$> getDoublele+    | t == mySQLTypeTimestamp+        || t == mySQLTypeTimestamp2   = do+            n <- getLenEncInt+            case n of+               0 -> pure $ timeStampValue (RawDate 0 0 0) (TimeOfDay 0 0 0)+               4 -> do+                   d <- getBinaryDateParts+                   pure $ timeStampValue d (TimeOfDay 0 0 0)+               7 -> do+                   d <- getBinaryDateParts+                   td <- TimeOfDay <$> getInt8' <*> getInt8' <*> getSecond4+                   pure $ timeStampValue d td+               11 -> do+                   d <- getBinaryDateParts+                   td <- TimeOfDay <$> getInt8' <*> getInt8' <*> getSecond8+                   pure $ timeStampValue d td+               _ -> fail "Database.MySQL.Protocol.MySQLValue: wrong TIMESTAMP length"+    | t == mySQLTypeDateTime+        || t == mySQLTypeDateTime2    = do+            n <- getLenEncInt+            case n of+               0 -> pure $ dateTimeValue (RawDate 0 0 0) (TimeOfDay 0 0 0)+               4 -> do+                   d <- getBinaryDateParts+                   pure $ dateTimeValue d (TimeOfDay 0 0 0)+               7 -> do+                   d <- getBinaryDateParts+                   td <- TimeOfDay <$> getInt8' <*> getInt8' <*> getSecond4+                   pure $ dateTimeValue d td+               11 -> do+                   d <- getBinaryDateParts+                   td <- TimeOfDay <$> getInt8' <*> getInt8' <*> getSecond8+                   pure $ dateTimeValue d td+               _ -> fail "Database.MySQL.Protocol.MySQLValue: wrong DATETIME length"++    | t == mySQLTypeDate+        || t == mySQLTypeNewDate      = do+            n <- getLenEncInt+            case n of+               0 -> pure $ dateValue (RawDate 0 0 0)+               4 -> dateValue <$> getBinaryDateParts+               _ -> fail "Database.MySQL.Protocol.MySQLValue: wrong DATE length"++    | t == mySQLTypeTime+        || t == mySQLTypeTime2        = do+            n <- getLenEncInt+            case n of+               0 -> pure $ MySQLTime 0 (TimeOfDay 0 0 0)+               8 -> do+                   sign <- getWord8   -- is_negative(1 if minus, 0 for plus)+                   d <- fromIntegral <$> getWord32le+                   h <-  getInt8'+                   MySQLTime sign <$> (TimeOfDay (d*24 + h) <$> getInt8' <*> getSecond4)++               12 -> do+                   sign <- getWord8   -- is_negative(1 if minus, 0 for plus)+                   d <- fromIntegral <$> getWord32le+                   h <-  getInt8'+                   MySQLTime sign <$> (TimeOfDay (d*24 + h) <$> getInt8' <*> getSecond8)+               _ -> fail "Database.MySQL.Protocol.MySQLValue: wrong TIME length"++    | t == mySQLTypeGeometry          = MySQLGeometry <$> getLenEncBytes+    | t == mySQLTypeVarChar+        || t == mySQLTypeEnum+        || t == mySQLTypeSet+        || t == mySQLTypeTinyBlob+        || t == mySQLTypeMediumBlob+        || t == mySQLTypeLongBlob+        || t == mySQLTypeBlob+        || t == mySQLTypeVarString+        || t == mySQLTypeString       = getLenEncBytes >>= stringColumnValue f+    | t == mySQLTypeBit               = MySQLBit <$> (getBits =<< getLenEncInt)+    | otherwise                       = fail $ "Database.MySQL.Protocol.MySQLValue:\+                                               \ missing binary decoder for " ++ show t+  where+    t = columnType f+    isUnsigned = flagUnsigned (columnFlags f)+    fracLexer bs = fst <$> LexFrac.readSigned LexFrac.readDecimal bs+    getInt8' :: Get Int+    getInt8' = fromIntegral <$> getWord8+    getSecond4 :: Get Pico+    getSecond4 = realToFrac <$> getWord8+    getSecond8 :: Get Pico+    getSecond8 =  do+        s <- getInt8'+        ms <- fromIntegral <$> getWord32le :: Get Int+        pure $! (realToFrac s + realToFrac ms / 1000000 :: Pico)+++getBinaryDateParts :: Get RawDate+getBinaryDateParts = RawDate <$> getWord16le <*> getWord8 <*> getWord8++-- | Get a bit sequence as a Word64+--+-- Since 'Word64' has a @Bits@ instance, it's easier to deal with in haskell.+--+getBits :: Int -> Get Word64+getBits bytes =+    if  | bytes == 0 || bytes == 1 -> fromIntegral <$> getWord8+        | bytes == 2 -> fromIntegral <$> getWord16be+        | bytes == 3 -> fromIntegral <$> getWord24be+        | bytes == 4 -> fromIntegral <$> getWord32be+        | bytes == 5 -> getWord40be+        | bytes == 6 -> getWord48be+        | bytes == 7 -> getWord56be+        | bytes == 8 -> getWord64be+        | otherwise  -> fail $  "Database.MySQL.Protocol.MySQLValue: \+                                \wrong bit length size: " ++ show bytes+{-# INLINE getBits #-}+++--------------------------------------------------------------------------------+-- | Binary protocol encoder+putBinaryField :: MySQLValue -> Put+putBinaryField (MySQLDecimal    n) = putLenEncBytes . L.toStrict . BB.toLazyByteString $+                                        formatScientificBuilder Fixed Nothing n+putBinaryField (MySQLInt8U      n) = putWord8 n+putBinaryField (MySQLInt8       n) = putWord8 (fromIntegral n)+putBinaryField (MySQLInt16U     n) = putWord16le n+putBinaryField (MySQLInt16      n) = putInt16le n+putBinaryField (MySQLInt32U     n) = putWord32le n+putBinaryField (MySQLInt32      n) = putInt32le n+putBinaryField (MySQLInt64U     n) = putWord64le n+putBinaryField (MySQLInt64      n) = putInt64le n+putBinaryField (MySQLFloat      x) = putFloatle x+putBinaryField (MySQLDouble     x) = putDoublele x+putBinaryField (MySQLYear       n) = putWord16le n+putBinaryField (MySQLTimeStamp (LocalTime date time)) = do putWord8 11    -- always put full+                                                           putBinaryDay date+                                                           putBinaryTime' time+putBinaryField (MySQLDateTime  (LocalTime date time)) = do putWord8 11    -- always put full+                                                           putBinaryDay date+                                                           putBinaryTime' time+putBinaryField (MySQLDate    d)    = do putWord8 4+                                        putBinaryDay d+putBinaryField (MySQLDateRaw d)    = do putWord8 4+                                        putRawDate d+putBinaryField (MySQLDateTimeRaw d time) = do putWord8 11+                                              putRawDate d+                                              putBinaryTime' time+putBinaryField (MySQLTimeStampRaw d time) = do putWord8 11+                                               putRawDate d+                                               putBinaryTime' time+putBinaryField (MySQLTime sign t)  = do putWord8 12    -- always put full+                                        putWord8 sign+                                        putBinaryTime t+putBinaryField (MySQLGeometry bs)  = putLenEncBytes bs+putBinaryField (MySQLBytes  bs)    = putLenEncBytes bs+putBinaryField (MySQLBit    word)  = putWord64le word+putBinaryField (MySQLText    t)    = putLenEncBytes (T.encodeUtf8 t)+putBinaryField MySQLNull           = return ()++putBinaryDay :: Day -> Put+putBinaryDay d = do let (yyyy, mm, dd) = toGregorian d+                    putWord16le (fromIntegral yyyy)+                    putWord8 (fromIntegral mm)+                    putWord8 (fromIntegral dd)+{-# INLINE putBinaryDay #-}++putRawDate :: RawDate -> Put+putRawDate (RawDate year month day) = putWord16le year >> putWord8 month >> putWord8 day++putBinaryTime' :: TimeOfDay -> Put+putBinaryTime' (TimeOfDay hh mm ss) = do let s = floor ss+                                             ms = floor $ (ss - realToFrac s) * 1000000+                                         putWord8 (fromIntegral hh)+                                         putWord8 (fromIntegral mm)+                                         putWord8 s+                                         putWord32le ms+{-# INLINE putBinaryTime' #-}++putBinaryTime :: TimeOfDay -> Put+putBinaryTime (TimeOfDay hh mm ss) = do let s = floor ss+                                            ms = floor $ (ss - realToFrac s) * 1000000+                                            (d, h) = hh `quotRem` 24  -- hour may exceed 24 here+                                        putWord32le (fromIntegral d)+                                        putWord8 (fromIntegral h)+                                        putWord8 (fromIntegral mm)+                                        putWord8 s+                                        putWord32le ms+{-# INLINE putBinaryTime #-}++--------------------------------------------------------------------------------+-- | Binary row decoder+--+-- MySQL use a special null bitmap without offset = 2 here.+--+getBinaryRow :: [ColumnDef] -> Int -> Get [MySQLValue]+getBinaryRow fields flen = do+    skipN 1           -- 0x00+    let maplen = (flen + 7 + 2) `shiftR` 3+    nullmap <- BitMap <$> getByteString maplen+    go fields nullmap 0+  where+    go :: [ColumnDef] -> BitMap -> Int -> Get [MySQLValue]+    go []     _       _   = pure []+    go (f:fs) nullmap pos = do+        r <- if isColumnNull nullmap pos+                then return MySQLNull+                else getBinaryField f+        let pos' = pos + 1+        rest <- pos' `seq` go fs nullmap pos'+        return (r `seq` (r : rest))+{-# INLINE getBinaryRow #-}++getBinaryRowVector :: V.Vector ColumnDef -> Int -> Get (V.Vector MySQLValue)+getBinaryRowVector fields flen = do+    skipN 1           -- 0x00+    let maplen = (flen + 7 + 2) `shiftR` 3+    nullmap <- BitMap <$> getByteString maplen+    (`V.imapM` fields) $ \ pos f ->+        if isColumnNull nullmap pos then return MySQLNull else getBinaryField f+{-# INLINE getBinaryRowVector #-}++--------------------------------------------------------------------------------+-- | Use 'ByteString' to present a bitmap.+--+-- When used for represent bits values, the underlining 'ByteString' follows:+--+--  * byteString: head       -> tail+--  * bit:        high bit   -> low bit+--+-- When used as a null-map/present-map, every bit inside a byte+-- is mapped to a column, the mapping order is following:+--+--  * byteString: head -> tail+--  * column:     left -> right+--+-- We don't use 'Int64' here because there maybe more than 64 columns.+--+newtype BitMap = BitMap { fromBitMap :: ByteString } deriving (Eq, Show)++-- | Test if a column is set(binlog protocol).+--+-- The number counts from left to right.+--+isColumnSet :: BitMap -> Int -> Bool+isColumnSet (BitMap bitmap) pos =+  let i = pos `unsafeShiftR` 3+      j = pos .&. 7+  in (bitmap `B.unsafeIndex` i) `testBit` j+{-# INLINE isColumnSet #-}++-- | Test if a column is null(binary protocol).+--+-- The number counts from left to right.+--+isColumnNull :: BitMap -> Int -> Bool+isColumnNull (BitMap nullmap) pos =+  let+    pos' = pos + 2+    i    = pos' `unsafeShiftR` 3+    j    = pos' .&. 7+  in (nullmap `B.unsafeIndex` i) `testBit` j+{-# INLINE isColumnNull #-}++-- | Make a nullmap for params(binary protocol) without offset.+--+makeNullMap :: [MySQLValue] -> BitMap+makeNullMap values = BitMap . B.pack $ go values 0x00 0+  where+    go :: [MySQLValue] -> Word8 -> Int -> [Word8]+    go []             byte   8  = [byte]+    go vs             byte   8  = byte : go vs 0x00 0+    go []             byte   _  = [byte]+    go (MySQLNull:vs) byte pos  = let pos' = pos + 1+                                      byte' = byte .|. bit pos+                                  in pos' `seq` byte' `seq` go vs byte' pos'+    go (_        :vs) byte pos  = let pos' = pos + 1 in pos' `seq` go vs byte pos'++--------------------------------------------------------------------------------+-- TODO: add helpers to parse mySQLTypeGEOMETRY+-- reference: https://github.com/felixge/node-mysql/blob/master/lib/protocol/Parser.js
+ src/Database/MySQL/Protocol/Packet.hs view
@@ -0,0 +1,360 @@+{-# OPTIONS_GHC -funbox-strict-fields #-}++{-|+Module      : Database.MySQL.Protocol.Packet+Description : MySQL packet type and various helpers.+Copyright   : (c) Winterland, 2016+License     : BSD+Maintainer  : drkoster@qq.com+Stability   : experimental+Portability : PORTABLE++MySQL packet decoder&encoder, and varities utility.++-}++module Database.MySQL.Protocol.Packet where++import           Control.Exception     (Exception (..), throwIO)+import           Data.Binary.Parser+import           Data.Binary.Put+import           Data.Binary           (Binary(..), encode)+import           Data.Bits+import qualified Data.ByteString       as B+import           Data.ByteString.Char8 as BC+import qualified Data.ByteString.Lazy  as L+import           Data.Int.Int24+import           Data.Int+import           Data.Word+import           Data.Typeable+import           Data.Word.Word24+import qualified Unwitch.Convert.Word16 as Word16+import qualified Unwitch.Convert.Word32 as Word32+import qualified Unwitch.Convert.Word64 as Word64+import qualified Unwitch.Convert.Word8  as Word8++--------------------------------------------------------------------------------+-- | MySQL packet type+--+data Packet = Packet+    { pLen  :: !Int64+    , pSeqN :: !Word8+    , pBody :: !L.ByteString+    } deriving (Show, Eq)++putPacket :: Packet -> Put+putPacket (Packet len seqN body)  = do+    putWord24le (fromIntegral len)+    putWord8 seqN+    putLazyByteString body+{-# INLINE putPacket #-}++getPacket :: Get Packet+getPacket = do+    len <- fromIntegral <$> getWord24le+    seqN <- getWord8+    body <- getLazyByteString len+    return (Packet len seqN body)+{-# INLINE getPacket #-}++instance Binary Packet where+    put = putPacket+    {-# INLINE put #-}+    get = getPacket+    {-# INLINE get #-}++isERR :: Packet -> Bool+isERR = packetStartsWith 0xFF+{-# INLINE isERR #-}++isOK :: Packet -> Bool+isOK = packetStartsWith 0x00+{-# INLINE isOK #-}++-- | A row can start with 0xFE too: the length prefix of a text field of+-- 16 MiB or more. Such a row is at least 9 bytes long, an EOF packet at most 5.+isEOF :: Packet -> Bool+isEOF p = pLen p < 9 && packetStartsWith 0xFE p+{-# INLINE isEOF #-}++-- | Is this an AuthMoreData packet? (first byte 0x01)+-- Used during authentication handshake for caching_sha2_password.+isAuthMoreData :: Packet -> Bool+isAuthMoreData = packetStartsWith 0x01+{-# INLINE isAuthMoreData #-}++-- | Is this an AuthSwitchRequest packet? (first byte 0xFE)+-- Same marker as EOF but used in authentication context.+isAuthSwitch :: Packet -> Bool+isAuthSwitch = packetStartsWith 0xFE+{-# INLINE isAuthSwitch #-}++-- | False for an empty packet, which has no marker byte to classify it by.+packetStartsWith :: Word8 -> Packet -> Bool+packetStartsWith marker p = case L.uncons (pBody p) of+    Just (firstByte, _) -> firstByte == marker+    Nothing -> False+{-# INLINE packetStartsWith #-}++-- | Is there more packet to be read?+--+--  https://dev.mysql.com/doc/internals/en/status-flags.html+isThereMore :: OK -> Bool+isThereMore p  = okStatus p .&. 0x08 /= 0+{-# INLINE isThereMore #-}++-- | Decoding packet inside IO, throw 'DecodePacketException' on fail parsing,+-- here we choose stability over correctness by omit incomplete consumed case:+-- if we successful parse a packet, then we don't care if there're bytes left.+--+decodeFromPacket :: Binary a => Packet -> IO a+decodeFromPacket = getFromPacket get+{-# INLINE decodeFromPacket #-}++getFromPacket :: Get a -> Packet -> IO a+getFromPacket g (Packet _ _ body) = case parseDetailLazy g body of+    Left  (buf, offset, errmsg) -> throwIO (DecodePacketFailed buf offset errmsg)+    Right (_,   _,      r     ) -> return r+{-# INLINE getFromPacket #-}++data DecodePacketException = DecodePacketFailed ByteString ByteOffset String+  deriving (Typeable, Show)+instance Exception DecodePacketException++encodeToPacket :: Binary a => Word8 -> a -> Packet+encodeToPacket seqN payload =+    let s = encode payload+        l = L.length s+    in Packet l seqN s+{-# INLINE encodeToPacket #-}++putToPacket :: Word8 -> Put -> Packet+putToPacket seqN payload =+    let s = runPut payload+        l = L.length s+    in Packet l seqN s+{-# INLINE putToPacket #-}++--------------------------------------------------------------------------------+-- OK, ERR, EOF++-- Decision: okLastInsertID is a Word64 because the server sends the unsigned+-- 64-bit value of LAST_INSERT_ID(), which an Int wraps above 2^63.+-- okAffectedRows stays an Int (changing it would break every+-- @sum . map okAffectedRows@ for a count no table reaches); a count above+-- maxBound fails to decode rather than wrap, see getLenEncInt.++-- | You may get interested in 'OK' packet because it provides information about+-- successful operations.+--+data OK = OK+    { okAffectedRows :: !Int      -- ^ affected row number+    , okLastInsertID :: !Word64   -- ^ last insert's ID+    , okStatus       :: !Word16+    , okWarningCnt   :: !Word16+    } deriving (Show, Eq)++getOK :: Get OK+getOK = OK <$ skipN 1+           <*> getLenEncInt+           <*> getLenEncWord64+           <*> getWord16le+           <*> getWord16le+{-# INLINE getOK #-}++putOK :: OK -> Put+putOK (OK row lid stat wcnt) = do+    putWord8 0x00+    putLenEncInt row+    putLenEncWord64 lid+    putWord16le stat+    putWord16le wcnt+{-# INLINE putOK #-}++instance Binary OK where+    get = getOK+    {-# INLINE get #-}+    put = putOK+    {-# INLINE put #-}++data ERR = ERR+    { errCode  :: !Word16+    , errState :: !ByteString+    , errMsg   :: !ByteString+    } deriving (Show, Eq)++getERR :: Get ERR+getERR = ERR <$  skipN 1+             <*> getWord16le+             <*  skipN 1+             <*> getByteString 5+             <*> getRemainingByteString+{-# INLINE getERR #-}++putERR :: ERR -> Put+putERR (ERR code stat msg) = do+    putWord8 0xFF+    putWord16le code+    putWord8 35 -- '#'+    putByteString stat+    putByteString msg+{-# INLINE putERR #-}++instance Binary ERR where+    get = getERR+    {-# INLINE get #-}+    put = putERR+    {-# INLINE put #-}++data EOF = EOF+    { eofWarningCnt :: !Word16+    , eofStatus     :: !Word16+    } deriving (Show, Eq)++getEOF :: Get EOF+getEOF = EOF <$  skipN 1+             <*> getWord16le+             <*> getWord16le+{-# INLINE getEOF #-}++putEOF :: EOF -> Put+putEOF (EOF wcnt stat) = do+    putWord8 0xFE+    putWord16le wcnt+    putWord16le stat+{-# INLINE putEOF #-}++instance Binary EOF where+    get = getEOF+    {-# INLINE get #-}+    put = putEOF+    {-# INLINE put #-}++--------------------------------------------------------------------------------+--  Helpers++getByteStringNul :: Get ByteString+getByteStringNul = L.toStrict <$> getLazyByteStringNul+{-# INLINE getByteStringNul #-}++getRemainingByteString :: Get ByteString+getRemainingByteString = L.toStrict <$> getRemainingLazyByteString+{-# INLINE getRemainingByteString #-}++putLenEncBytes :: ByteString -> Put+putLenEncBytes c = do+    putLenEncInt (B.length c)+    putByteString c+{-# INLINE putLenEncBytes #-}++getLenEncBytes :: Get ByteString+getLenEncBytes = getLenEncInt >>= getByteString+{-# INLINE getLenEncBytes #-}++-- | length encoded int+-- https://dev.mysql.com/doc/internals/en/integer.html#packet-Protocol::LengthEncodedInteger+--+-- Fails on a value above @maxBound :: Int@; 'getLenEncWord64' reads the full range.+getLenEncInt:: Get Int+getLenEncInt = getWord8 >>= word2Len+  where+    word2Len l+         | l <  0xFB  = pure (fromIntegral l)+         | l == 0xFC  = fromIntegral <$> getWord16le+         | l == 0xFD  = fromIntegral <$> getWord24le+         | l == 0xFE  = getWord64le >>= lenEncWord64ToInt+         | otherwise = fail $ "invalid length val " ++ show l+{-# INLINE getLenEncInt #-}++lenEncWord64ToInt :: Word64 -> Get Int+lenEncWord64ToInt value = case Word64.toInt value of+    Just int -> pure int+    Nothing -> fail ("length-encoded integer " ++ show value ++ " does not fit in an Int")++-- | A length-encoded integer over its full unsigned 64-bit range.+getLenEncWord64 :: Get Word64+getLenEncWord64 = do+    marker <- getWord8+    if  | marker < 0xFB -> pure (Word8.toWord64 marker)+        | marker == 0xFC -> Word16.toWord64 <$> getWord16le+        | marker == 0xFD -> Word32.toWord64 <$> getWord24le+        | marker == 0xFE -> getWord64le+        | otherwise -> fail ("invalid length-encoded integer marker " ++ show marker)++putLenEncWord64 :: Word64 -> Put+putLenEncWord64 value = case Word64.toWord8 value of+    Just byte -> if byte < 0xFB+        then putWord8 byte+        else putWord8 0xFC >> putWord16le (Word8.toWord16 byte)+    Nothing -> case Word64.toWord16 value of+        Just short -> putWord8 0xFC >> putWord16le short+        Nothing -> case Word64.toWord32 value of+            Just word -> if word < 0x1000000+                then putWord8 0xFD >> putWord24le word+                else putWord8 0xFE >> putWord64le value+            Nothing -> putWord8 0xFE >> putWord64le value++putLenEncInt:: Int -> Put+putLenEncInt x+         | x <  251      = putWord8 (fromIntegral x)+         | x < 65536     = putWord8 0xFC >> putWord16le (fromIntegral x)+         | x < 16777216  = putWord8 0xFD >> putWord24le (fromIntegral x)+         | otherwise     = putWord8 0xFE >> putWord64le (fromIntegral x)+{-# INLINE putLenEncInt #-}++putWord24le :: Word32 -> Put+putWord24le v = do+    putWord16le $ fromIntegral v+    putWord8 $ fromIntegral (v `shiftR` 16)+{-# INLINE putWord24le #-}++getWord24le :: Get Word32+getWord24le = do+    a <- fromIntegral <$> getWord16le+    b <- fromIntegral <$> getWord8+    return $! a .|. (b `shiftL` 16)+{-# INLINE getWord24le #-}++putWord48le :: Word64 -> Put+putWord48le v = do+    putWord32le $ fromIntegral v+    putWord16le $ fromIntegral (v `shiftR` 32)+{-# INLINE putWord48le #-}++getWord48le :: Get Word64+getWord48le = do+    a <- fromIntegral <$> getWord32le+    b <- fromIntegral <$> getWord16le+    return $! a .|. (b `shiftL` 32)+{-# INLINE getWord48le #-}++getWord24be :: Get Word24+getWord24be = do+    a <- fromIntegral <$> getWord16be+    b <- fromIntegral <$> getWord8+    return $! b .|. (a `shiftL` 8)+{-# INLINE getWord24be #-}++getInt24be :: Get Int24+getInt24be = do+    a <- fromIntegral <$> getWord16be+    b <- fromIntegral <$> getWord8+    return $! fromIntegral $ (b .|. (a `shiftL` 8) :: Word24)+{-# INLINE getInt24be #-}++getWord40be, getWord48be, getWord56be :: Get Word64+getWord40be = do+    a <- fromIntegral <$> getWord32be+    b <- fromIntegral <$> getWord8+    return $! (a `shiftL` 8) .|. b+getWord48be = do+    a <- fromIntegral <$> getWord32be+    b <- fromIntegral <$> getWord16be+    return $! (a `shiftL` 16) .|. b+getWord56be = do+    a <- fromIntegral <$> getWord32be+    b <- fromIntegral <$> getWord24be+    return $! (a `shiftL` 24) .|. b+{-# INLINE getWord40be #-}+{-# INLINE getWord48be #-}+{-# INLINE getWord56be #-}
+ src/Database/MySQL/Protocol/RawRow.hs view
@@ -0,0 +1,231 @@+{-# LANGUAGE UnboxedSums   #-}+{-# LANGUAGE UnboxedTuples #-}++{-|+Module      : Database.MySQL.Protocol.RawRow+Description : Rows as their bytes plus the bounds of each field+Copyright   : (c) Jappie Klooster, 2026+License     : BSD3+Maintainer  : hi@jappie.me+Stability   : experimental+Portability : PORTABLE++A 'RawRow' is a row's bytes plus where each of its fields starts and how long+it is, found in one pass over the row. A field can then be read by its column+number without walking the row again, which is what a library decoding rows by+column number needs. "Database.MySQL.Decoder" reads 'RawRow's.+-}+module Database.MySQL.Protocol.RawRow+  ( -- * Protocols+    TextProtocol+  , BinaryProtocol+    -- * Raw rows+  , RawRow+  , rawRowBytes+  , rawRowFieldCount+  , RawField(..)+  , ColumnNumber(..)+  , rawField+    -- * Building raw rows+  , textRawRow+  , BinaryWidth(..)+  , binaryWidth+  , binaryRawRow+    -- * Internal utilities+  , binaryNullMapLength+  , isNullInMap+  ) where++import           Control.Monad.ST                   (ST, runST)+import           Data.Bits                          (testBit, unsafeShiftR,+                                                     (.&.))+import           Data.ByteString                    (ByteString)+import qualified Data.ByteString                    as B+import qualified Data.ByteString.Unsafe             as B+import           Data.Kind                          (Type)+import qualified Data.Vector                        as V+import qualified Data.Vector.Unboxed                as VU+import qualified Data.Vector.Unboxed.Mutable        as VUM+import           Database.MySQL.Protocol.ColumnDef+import           Database.MySQL.Protocol.MySQLValue (ColumnNumber (..),+                                                     RowError (..),+                                                     RowErrorKind (..),+                                                     lengthEncodedInt,+                                                     nextColumn)++-- | Rows of a plain query, whose fields are text.+--+-- @since 1.4.0+data TextProtocol++-- | Rows of a prepared statement, whose fields are binary.+--+-- @since 1.4.0+data BinaryProtocol++-- | A row's bytes and the bounds of each of its fields. The @protocol@,+-- 'TextProtocol' or 'BinaryProtocol', says how the fields are encoded, so a+-- decoder for the other protocol cannot be run on it.+--+-- @since 1.4.0+data RawRow (protocol :: Type) = RawRow+    { rawRowBytes  :: !ByteString+      -- ^ the row packet's body+    , rawRowBounds :: !(VU.Vector Int)+      -- ^ field @i@ starts at element @2i@ and its length is element @2i + 1@,+      -- or 'nullFieldLength' for a NULL+    }++-- | @since 1.4.0+rawRowFieldCount :: RawRow protocol -> Int+rawRowFieldCount row = VU.length (rawRowBounds row) `unsafeShiftR` 1++-- | One field of a 'RawRow'.+--+-- @since 1.4.0+data RawField+    = RawNull+    | RawBytes !ByteString+      -- ^ the field's bytes, without a length prefix: the value as text in+      -- 'TextProtocol', its binary encoding in 'BinaryProtocol'+    | RawAbsent+      -- ^ the row has no field with that column number+    deriving (Show, Eq)++-- | The field of a column, counting from 0. Inlined so that a caller matching+-- on the result never allocates the 'RawField'.+--+-- @since 1.4.0+rawField :: RawRow protocol -> ColumnNumber -> RawField+rawField row@(RawRow bytes bounds) (ColumnNumber column) =+    -- Compared with the field count, not @2 * column + 1@ with the bounds'+    -- length: doubling a large column number overflows and passes the check.+    if column < 0 || column >= rawRowFieldCount row+    then RawAbsent+    else+        let start       = VU.unsafeIndex bounds (2 * column)+            fieldLength = VU.unsafeIndex bounds (2 * column + 1)+        in if fieldLength == nullFieldLength+           then RawNull+           else RawBytes (B.unsafeTake fieldLength (B.unsafeDrop start bytes))+{-# INLINE rawField #-}++nullFieldLength :: Int+nullFieldLength = -1++-- | The bounds of the first @columnCount@ fields of a text-protocol row. Bytes+-- after the last field are ignored, as 'decodeTextRow' ignores them.+--+-- @since 1.4.0+textRawRow :: Int -> ByteString -> Either RowError (RawRow TextProtocol)+textRawRow columnCount row = runST $ do+    bounds <- VUM.unsafeNew (2 * columnCount)+    outcome <- fillTextBounds row bounds columnCount (ColumnNumber 0) 0+    case outcome of+        Left rowError -> pure (Left rowError)+        Right ()      -> Right . RawRow row <$> VU.unsafeFreeze bounds++-- | Records the bounds of the fields from @column@ on, the first of which+-- starts at byte @offset@.+fillTextBounds :: ByteString -> VUM.MVector s Int -> Int -> ColumnNumber -> Int -> ST s (Either RowError ())+fillTextBounds row bounds columnCount column@(ColumnNumber number) offset =+    if  | number >= columnCount -> pure (Right ())+        | offset >= B.length row -> pure (Left (RowError column offset RowEndsEarly))+        | B.unsafeIndex row offset == 0xFB -> do+            writeBounds bounds column offset nullFieldLength+            fillTextBounds row bounds columnCount (nextColumn column) (offset + 1)+        | otherwise -> case lengthEncodedInt row offset of+            (# kind | #) -> pure (Left (RowError column offset kind))+            (# | (# fieldLength, fieldStart #) #) ->+                if fieldLength > B.length row - fieldStart+                then pure (Left (RowError column offset RowEndsEarly))+                else do+                    writeBounds bounds column fieldStart fieldLength+                    fillTextBounds row bounds columnCount (nextColumn column) (fieldStart + fieldLength)++writeBounds :: VUM.MVector s Int -> ColumnNumber -> Int -> Int -> ST s ()+writeBounds bounds (ColumnNumber column) start fieldLength = do+    VUM.unsafeWrite bounds (2 * column) start+    VUM.unsafeWrite bounds (2 * column + 1) fieldLength++-- | How the binary protocol lays out the non-NULL fields of a column.+--+-- @since 1.4.0+data BinaryWidth+    = BinaryFixed !Int+      -- ^ always this many bytes: the integers, FLOAT, DOUBLE and YEAR+    | BinaryLengthEncoded+      -- ^ a length, then that many bytes: strings, DECIMAL, BIT, and the dates+      -- and times, whose length says which of their parts are present+    | BinaryNoBytes+      -- ^ a column of type NULL, whose fields the NULL map marks+    deriving (Show, Eq)++-- | @since 1.4.0+binaryWidth :: ColumnDef -> BinaryWidth+binaryWidth column =+    if  | t == mySQLTypeTiny -> BinaryFixed 1+        | t == mySQLTypeShort || t == mySQLTypeYear -> BinaryFixed 2+        | t == mySQLTypeLong || t == mySQLTypeInt24 || t == mySQLTypeFloat -> BinaryFixed 4+        | t == mySQLTypeLongLong || t == mySQLTypeDouble -> BinaryFixed 8+        | t == mySQLTypeNull -> BinaryNoBytes+        | otherwise -> BinaryLengthEncoded+  where+    t = columnType column++-- | The bounds of the fields of a binary-protocol row: a 0x00 header, a NULL+-- map with a bit per column from bit 2 on, then the non-NULL fields, laid out+-- as each column's 'BinaryWidth' says.+--+-- @since 1.4.0+binaryRawRow :: V.Vector BinaryWidth -> ByteString -> Either RowError (RawRow BinaryProtocol)+binaryRawRow widths row =+    let nullMapLength = binaryNullMapLength (V.length widths)+    in if 1 + nullMapLength > B.length row+       then Left (RowError (ColumnNumber 0) 0 RowEndsEarly)+       else runST $ do+           bounds <- VUM.unsafeNew (2 * V.length widths)+           outcome <- fillBinaryBounds row widths bounds (ColumnNumber 0) (1 + nullMapLength)+           case outcome of+               Left rowError -> pure (Left rowError)+               Right ()      -> Right . RawRow row <$> VU.unsafeFreeze bounds++fillBinaryBounds :: ByteString -> V.Vector BinaryWidth -> VUM.MVector s Int -> ColumnNumber -> Int+                 -> ST s (Either RowError ())+fillBinaryBounds row widths bounds column@(ColumnNumber number) offset =+    if  | number >= V.length widths -> pure (Right ())+        | isNullInMap row column -> do+            writeBounds bounds column offset nullFieldLength+            fillBinaryBounds row widths bounds (nextColumn column) offset+        | otherwise -> case V.unsafeIndex widths number of+            BinaryNoBytes -> do+                writeBounds bounds column offset 0+                fillBinaryBounds row widths bounds (nextColumn column) offset+            BinaryFixed width ->+                if width > B.length row - offset+                then pure (Left (RowError column offset RowEndsEarly))+                else do+                    writeBounds bounds column offset width+                    fillBinaryBounds row widths bounds (nextColumn column) (offset + width)+            BinaryLengthEncoded ->+                if offset >= B.length row+                then pure (Left (RowError column offset RowEndsEarly))+                else case lengthEncodedInt row offset of+                    (# kind | #) -> pure (Left (RowError column offset kind))+                    (# | (# fieldLength, fieldStart #) #) ->+                        if fieldLength > B.length row - fieldStart+                        then pure (Left (RowError column offset RowEndsEarly))+                        else do+                            writeBounds bounds column fieldStart fieldLength+                            fillBinaryBounds row widths bounds (nextColumn column) (fieldStart + fieldLength)++-- | The bytes of a binary-protocol row's NULL map, which holds a bit per+-- column from bit 2 on.+binaryNullMapLength :: Int -> Int+binaryNullMapLength columnCount = (columnCount + 7 + 2) `unsafeShiftR` 3++-- | The NULL map starts after the header byte; column @i@ is bit @i + 2@. The+-- row must be long enough to hold the map.+isNullInMap :: ByteString -> ColumnNumber -> Bool+isNullInMap row (ColumnNumber column) =+    testBit (B.unsafeIndex row (1 + ((column + 2) `unsafeShiftR` 3))) ((column + 2) .&. 7)
+ src/Database/MySQL/Query.hs view
@@ -0,0 +1,175 @@+module Database.MySQL.Query where++import           Data.String               (IsString (..))+import           Control.Exception         (throw, Exception)+import           Data.Typeable+import qualified Data.ByteString           as B+import qualified Data.ByteString.Lazy      as L+import qualified Data.ByteString.Builder   as BB+import           Data.Word                 (Word8)+import           Control.Arrow             (first)+import           Database.MySQL.Protocol.MySQLValue+import           Data.Binary.Put++-- | Query string type borrowed from @mysql-simple@.+--+-- This type is intended to make it difficult to+-- construct a SQL query by concatenating string fragments, as that is+-- an extremely common way to accidentally introduce SQL injection+-- vulnerabilities into an application.+--+-- This type is an instance of 'IsString', so the easiest way to+-- construct a query is to enable the @OverloadedStrings@ language+-- extension and then simply write the query in double quotes.+--+-- The underlying type is a 'L.ByteString', and literal Haskell strings+-- that contain Unicode characters will be correctly transformed to+-- UTF-8.+--+newtype Query = Query { fromQuery :: L.ByteString } deriving (Eq, Ord, Typeable)++instance Show Query where+    show = show . fromQuery++instance Read Query where+    readsPrec i = fmap (first Query) . readsPrec i++instance IsString Query where+    fromString = Query . BB.toLazyByteString . BB.stringUtf8++-- | A type to wrap a query parameter in to allow for single and multi-valued parameters.+--+-- The behavior of 'Param' can be illustrated by following example:+--+-- @+--    render $ One (MySQLText "hello") = hello+--    render $ Many [MySQLText "hello", MySQLText "world"] = hello, world+--    render $ Many [] = null+-- @+--+-- So you can now write a query like this: @ SELECT * FROM test WHERE _id IN (?, 888) @+-- and use 'Many' 'Param' to fill the hole. There's no equivalent for prepared statement sadly.+--+data Param = One  MySQLValue+           | Many [MySQLValue]++-- | A type that may be used as a single parameter to a SQL query. Inspired from @mysql-simple@.+class QueryParam a where+    render :: a -> Put+    -- ^ Prepare a value for substitution into a query string.++instance QueryParam Param where+    render (One x)      = putTextField x+    render (Many [])    = putTextField MySQLNull+    render (Many (x:[]))= putTextField x+    render (Many (x:xs))= do putTextField x+                             mapM_ (\f -> putCharUtf8 ',' >> putTextField f) xs++instance QueryParam MySQLValue where+    render = putTextField++-- | Fill the query's @?@ placeholders with the rendered parameters, in order.+--+-- A @?@ inside a quoted string or identifier (@\'...\'@, @\"...\"@,+-- @\`...\`@) or a comment (@-- @, @#@, @\/* *\/@) is not a placeholder. The+-- quotes are read with MySQL's default escaping, where a backslash escapes+-- the next character; under @NO_BACKSLASH_ESCAPES@ a string ending in a+-- backslash, such as @\'C:\\\'@, looks unterminated, so the placeholders+-- after it are missed. In that mode a backslash in a rendered value is+-- stored twice, see "Database.MySQL.Protocol.Escape".+--+-- Throws 'WrongParamsCount' when the number of placeholders and parameters+-- differ.+renderParams :: QueryParam p => Query -> [p] -> Query+renderParams (Query qry) params =+    let fragments = map L.fromStrict (splitAtPlaceholders (L.toStrict qry))+    in Query . runPut $ merge fragments params+  where+    merge [x]    []     = putLazyByteString x+    merge (x:xs) (y:ys) = putLazyByteString x >> render y >> merge xs ys+    merge _     _       = throw WrongParamsCount++-- | The query text between its placeholders, see 'renderParams': one more+-- fragment than there are placeholders.+splitAtPlaceholders :: B.ByteString -> [B.ByteString]+splitAtPlaceholders query = scanCode query 0 0++-- | Scan SQL outside quotes and comments; @start@ is where the current+-- fragment began.+scanCode :: B.ByteString -> Int -> Int -> [B.ByteString]+scanCode query start position = case byteAt query position of+    Nothing -> [B.drop start query]+    Just byte -> if+        | byte == questionMark ->+            fragment query start position : scanCode query (position + 1) (position + 1)+        | byte == singleQuote || byte == doubleQuote || byte == backtick ->+            scanQuoted query start byte (position + 1)+        | byte == hash ->+            scanLineComment query start (position + 1)+        | byte == dash && byteAt query (position + 1) == Just dash+            && opensDashComment (byteAt query (position + 2)) ->+            scanLineComment query start (position + 2)+        | byte == slash && byteAt query (position + 1) == Just star+            && not (isExecutableComment query (position + 2)) ->+            scanBlockComment query start (position + 2)+        | otherwise -> scanCode query start (position + 1)++-- | Scan inside a quote opened by @quote@. A doubled quote stands for the+-- quote character itself; a backslash escapes the next byte in strings but+-- not in backtick-quoted identifiers.+scanQuoted :: B.ByteString -> Int -> Word8 -> Int -> [B.ByteString]+scanQuoted query start quote position = case byteAt query position of+    Nothing -> [B.drop start query]+    Just byte -> if+        | byte == backslash && quote /= backtick -> scanQuoted query start quote (position + 2)+        | byte == quote -> if byteAt query (position + 1) == Just quote+            then scanQuoted query start quote (position + 2)+            else scanCode query start (position + 1)+        | otherwise -> scanQuoted query start quote (position + 1)++scanLineComment :: B.ByteString -> Int -> Int -> [B.ByteString]+scanLineComment query start position = case B.elemIndex newline (B.drop position query) of+    Nothing -> [B.drop start query]+    Just offset -> scanCode query start (position + offset + 1)++scanBlockComment :: B.ByteString -> Int -> Int -> [B.ByteString]+scanBlockComment query start position = case B.breakSubstring "*/" (B.drop position query) of+    (comment, rest) -> if B.null rest+        then [B.drop start query]+        else scanCode query start (position + B.length comment + 2)++-- | MySQL only takes @--@ for a comment when a space or control character+-- follows it, so @1--1@ stays arithmetic.+opensDashComment :: Maybe Word8 -> Bool+opensDashComment next = case next of+    Nothing -> True+    Just byte -> byte <= 0x20++-- | @\/*!@ and MariaDB's @\/*M!@ wrap SQL the server runs, so their+-- placeholders count.+isExecutableComment :: B.ByteString -> Int -> Bool+isExecutableComment query position =+    B.isPrefixOf "!" (B.drop position query) || B.isPrefixOf "M!" (B.drop position query)++-- | 'B.indexMaybe' needs bytestring 0.11, the bounds still admit 0.10.+byteAt :: B.ByteString -> Int -> Maybe Word8+byteAt query position =+    if position < B.length query then Just (B.index query position) else Nothing++fragment :: B.ByteString -> Int -> Int -> B.ByteString+fragment query start end = B.take (end - start) (B.drop start query)++questionMark, singleQuote, doubleQuote, backtick, backslash, hash, dash, slash, star, newline :: Word8+questionMark = 0x3F+singleQuote = 0x27+doubleQuote = 0x22+backtick = 0x60+backslash = 0x5C+hash = 0x23+dash = 0x2D+slash = 0x2F+star = 0x2A+newline = 0x0A++data WrongParamsCount = WrongParamsCount deriving (Show, Typeable)+instance Exception WrongParamsCount
+ src/Database/MySQL/TLS.hs view
@@ -0,0 +1,98 @@+{-|+Module      : Database.MySQL.Connection+Description : TLS support for mysql-haskell via @tls@ package.+Copyright   : (c) Winterland, 2016+License     : BSD+Maintainer  : drkoster@qq.com+Stability   : experimental+Portability : PORTABLE++This module provides secure MySQL connection using 'tls' package, please make sure your certificate is v3 extension enabled.++-}++module Database.MySQL.TLS (+      connect+    , connectDetail+    , connectDetailExtended+    , module Data.TLSSetting+    ) where++import           Control.Exception              (bracketOnError, throwIO)+import           Data.ByteString                (ByteString)+import qualified Data.ByteString                as B+import qualified Data.ByteString.Lazy           as L+import           Data.Word                      (Word8)+import qualified Data.Connection                as Conn+import           Data.TLSSetting+import           Database.MySQL.Connection      hiding (connect, connectDetail, connectDetailExtended)+import           Database.MySQL.Protocol.Auth+import           Database.MySQL.Protocol.Packet+import           System.IO.Streams               (InputStream)+import qualified Network.TLS                    as TLS+import qualified System.IO.Streams.TCP          as TCP+import qualified Data.Connection                as TCP+import qualified System.IO.Streams.TLS          as TLS++--------------------------------------------------------------------------------++-- | Provide a 'TLS.ClientParams' and a subject name to establish a TLS connection.+--+connect :: ConnectInfo -> (TLS.ClientParams, String) -> IO MySQLConn+connect c cp = fmap snd (connectDetail c cp)++connectDetail :: ConnectInfo -> (TLS.ClientParams, String) -> IO (Greeting, MySQLConn)+connectDetail c cp = fmap withoutNegotiated (connectDetailExtended noExtendedCapabilities c cp)++-- | 'connectDetail' asking a MariaDB server for extended capabilities, also+-- returning those the server agreed to. Asking for none gives exactly+-- 'connectDetail'. See Note [MariaDB extended capabilities].+--+-- @since 1.4.0+connectDetailExtended :: ExtendedCapabilities -> ConnectInfo -> (TLS.ClientParams, String)+                      -> IO (Greeting, ExtendedCapabilities, MySQLConn)+connectDetailExtended requested ci tlsParams =+    bracketOnError+        (TCP.connectSocket (ciHost ci) (ciPort ci) >>= TCP.socketToConnection bUFSIZE)+        TCP.close+        (handshakeTLS requested ci tlsParams)++handshakeTLS :: ExtendedCapabilities -> ConnectInfo -> (TLS.ClientParams, String) -> TCP.TCPConnection+             -> IO (Greeting, ExtendedCapabilities, MySQLConn)+handshakeTLS requested (ConnectInfo _host _port db user pass charset) (cparams, subName) c = do+    is' <- decodeInputStream (TCP.source c)+    p <- readPacket is'+    (greet, offered) <- getFromPacket getGreetingExtended p+    let negotiated = negotiateExtended requested greet offered+    if supportTLS (greetingCaps greet)+    then do+        let cparams' = cparams {+                    TLS.clientUseServerNameIndication = False+                ,   TLS.clientServerIdentification = (subName, "")+                }+        let (sock, sockAddr) = Conn.connExtraInfo c+        sendPacket c (sslRequestPacket negotiated charset)+        bracketOnError (TLS.contextNew sock cparams')+                       ( \ ctx -> TLS.bye ctx >> TCP.close c ) $ \ ctx -> do+            TLS.handshake ctx+            tc <- TLS.tLsToConnection (ctx, sockAddr)+            tlsIs' <- decodeInputStream (TCP.source tc)+            sendPacket tc (handshakeResponsePacket 2 negotiated db user pass charset greet)+            q <- readPacket tlsIs'+            completeAuth tlsIs' (sendPacket tc) pass q tlsFullAuth+            conn <- authenticatedConnection tlsIs' tc+            return (greet, negotiated, conn)+    else error "Database.MySQL.TLS: server doesn't support TLS connection"++-- | Full auth handler for TLS connections: sends the cleartext password+-- as a NUL-terminated packet, which MySQL accepts over encrypted connections.+tlsFullAuth :: Word8 -> ByteString -> (Packet -> IO ()) -> InputStream Packet -> IO ()+tlsFullAuth seqN pass writePacket is = do+    let payload = pass `B.append` "\0"+        body = L.fromStrict payload+        pkt = Packet (fromIntegral (B.length payload)) seqN body+    writePacket pkt+    q <- readPacket is+    if isOK q+        then return ()+        else decodeFromPacket q >>= throwIO . ERRException
+ src/System/IO/Streams/TCP.hs view
@@ -0,0 +1,175 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE CPP #-}++-- | This module provides convenience functions for interfacing raw tcp.+--+-- Please use 'E.bracket' or its friends to ensure exception safety.+--+-- This module is intended to be imported @qualified@, e.g.:+--+-- @+-- import           "Data.Connection"+-- import qualified "System.IO.Streams.TCP" as TCP+-- @+--+module System.IO.Streams.TCP+  ( TCPConnection+    -- * client+  , connect+  , connectSocket+  , socketToConnection+  , defaultChunkSize+    -- * server+  , bindAndListen+  , bindAndListenWith+  , accept+  , acceptWith+  ) where++import qualified Control.Exception         as E+import           Control.Monad+import           Data.Connection+import qualified Data.ByteString           as B+import qualified Data.ByteString.Lazy.Internal as L+import qualified Network.Socket            as N+import qualified Network.Socket.ByteString as NB+import qualified Network.Socket.ByteString.Lazy as NL+import qualified System.IO.Streams         as S+import           Foreign.Storable   (sizeOf)++addrAny :: N.HostAddress+#if MIN_VERSION_network(2,7,0)+addrAny = N.tupleToHostAddress (0,0,0,0)+#else+addrAny = N.iNADDR_ANY+#endif++-- | Type alias for tcp connection.+--+-- Normally you shouldn't use 'N.Socket' in 'connExtraInfo' directly, this field is+-- intend for used with 'N.setSocketOption' if you need to.+--+type TCPConnection = Connection (N.Socket, N.SockAddr)++-- | The chunk size used for I\/O, less the memory management overhead.+--+-- Currently set to 32k.+--+defaultChunkSize :: Int+defaultChunkSize = 32 * k - chunkOverhead+  where+    k = 1024+    chunkOverhead = 2 * sizeOf (undefined :: Int)++-- | Initiating an raw TCP connection to the given @('HostName', 'PortNumber')@ combination.+--+-- It use 'N.getAddrInfo' to resolve host/service name+-- with 'N.AI_ADDRCONFIG', 'N.AI_NUMERICSERV' hint set, so it should be able to+-- resolve both numeric IPv4/IPv6 hostname and domain name.+--+-- `TCP_NODELAY` are enabled by default. you can use 'N.setSocketOption' to adjust.+--+connectSocket :: N.HostName             -- ^ hostname to connect to+              -> N.PortNumber           -- ^ port number to connect to+              -> IO (N.Socket, N.SockAddr)+connectSocket host port = do+    (family, socketType, protocol, addr) <- resolveAddrInfo host port+    E.bracketOnError (N.socket family socketType protocol)+                     N.close+                     (\sock -> do N.connect sock addr+                                  N.setSocketOption sock N.NoDelay 1+                                  return (sock, addr)+                     )+  where+    resolveAddrInfo host' port' = do+        -- Partial function here OK, network will throw an exception rather than+        -- return the empty list here.+        (addrInfo:_) <- N.getAddrInfo (Just hints) (Just host') (Just $ show port')+        let family     = N.addrFamily addrInfo+        let socketType = N.addrSocketType addrInfo+        let protocol   = N.addrProtocol addrInfo+        let addr    = N.addrAddress addrInfo+        return (family, socketType, protocol, addr)+      where+        hints = N.defaultHints {+                N.addrFlags      = [N.AI_ADDRCONFIG, N.AI_NUMERICSERV]+            ,   N.addrSocketType = N.Stream+            }+    {-# INLINABLE resolveAddrInfo #-}++-- | Make a 'Connection' from a 'Socket' with given buffer size.+--+socketToConnection+    :: Int                      -- ^ receive buffer size+    -> (N.Socket, N.SockAddr)   -- ^ socket address pair+    -> IO TCPConnection+socketToConnection bufsiz (sock, addr) = do+    is <- S.makeInputStream $ do+        s <- NB.recv sock bufsiz+        return $! if B.null s then Nothing else Just s+    return (Connection is (send' sock) (N.close sock) (sock, addr))+  where+    send' _    (L.Empty) = return ()+    send' sock' (L.Chunk bs L.Empty) = unless (B.null bs) (NB.sendAll sock' bs)+    send' sock' lbs = NL.sendAll sock' lbs++-- | Connect to server using 'defaultChunkSize'.+--+connect :: N.HostName             -- ^ hostname to connect to+        -> N.PortNumber           -- ^ port number to connect to+        -> IO TCPConnection+connect host port = connectSocket host port >>= socketToConnection defaultChunkSize++-- | Bind and listen on port with a limit on connection count.+--+-- This function will set @SO_REUSEADDR@, @TCP_NODELAY@ before binding.+--+bindAndListen :: Int                 -- ^ connection limit+              -> N.PortNumber        -- ^ port number+              -> IO N.Socket+bindAndListen = bindAndListenWith $ \ sock -> do+    N.setSocketOption sock N.ReuseAddr 1+    N.setSocketOption sock N.NoDelay 1++-- | Bind and listen on port with a limit on connection count.+--+-- Note: The following socket options are inherited by a connected TCP socket from the listening socket:+--+-- @+-- SO_DEBUG+-- SO_DONTROUTE+-- SO_KEEPALIVE+-- SO_LINGER+-- SO_OOBINLINE+-- SO_RCVBUF+-- SO_RCVLOWAT+-- SO_SNDBUF+-- SO_SNDLOWAT+-- TCP_MAXSEG+-- TCP_NODELAY+-- @+--+bindAndListenWith :: (N.Socket -> IO ()) -- ^ set socket options before binding+                  -> Int                 -- ^ connection limit+                  -> N.PortNumber        -- ^ port number+                  -> IO N.Socket+bindAndListenWith f maxc port =+    E.bracketOnError (N.socket N.AF_INET N.Stream 0)+                     N.close+                     (\sock -> do f sock+                                  N.bind sock (N.SockAddrInet port addrAny)+                                  N.listen sock maxc+                                  return sock+                     )++-- | Accept a connection with 'defaultChunkSize'.+--+accept :: N.Socket -> IO TCPConnection+accept = acceptWith (socketToConnection defaultChunkSize)++-- | Accept a connection with user customization.+--+acceptWith :: ((N.Socket, N.SockAddr) -> IO TCPConnection) -- ^ set socket options, adjust receive buffer, etc.+           -> N.Socket+           -> IO TCPConnection+acceptWith f = f <=< N.accept
+ src/System/IO/Streams/TLS.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE ScopedTypeVariables #-}++-- | This module provides convenience functions for interfacing @tls@.+--+-- This module is intended to be imported @qualified@, e.g.:+--+-- @+-- import           "Data.Connection"+-- import qualified "System.IO.Streams.TLS" as TLS+-- @+--+module System.IO.Streams.TLS+  ( TLSConnection+    -- * client+  , connect+  , connectTLS+  , tLsToConnection+    -- * server+  , accept+    -- * re-export+  , module Data.TLSSetting+  ) where++import qualified Control.Exception     as E+import           Data.Connection+import qualified Data.ByteString       as B+import qualified Data.ByteString.Char8 as BC+import           Data.TLSSetting+import qualified Network.Socket        as N+import           Network.TLS           (ClientParams, Context, ServerParams)+import qualified Network.TLS           as TLS+import qualified System.IO.Streams     as Stream+import qualified System.IO.Streams.TCP as TCP+++-- | Type alias for tls connection.+--+-- Normally you shouldn't use 'TLS.Context' in 'connExtraInfo' directly.+--+type TLSConnection = Connection (TLS.Context, N.SockAddr)++-- | Make a 'Connection' from a 'Context'.+--+tLsToConnection :: (Context, N.SockAddr)    -- ^ TLS connection / socket address pair+                -> IO TLSConnection+tLsToConnection (ctx, addr) = do+    is <- Stream.makeInputStream input+    return (Connection is write (closeTLS ctx) (ctx, addr))+  where+    input = (do+        s <- TLS.recvData ctx+        return $! if B.null s then Nothing else Just s+        ) `E.catch` (\(_::E.SomeException) -> return Nothing)+    write s = TLS.sendData ctx s++-- | Close a TLS 'Context' and its underlying socket.+--+closeTLS :: Context -> IO ()+closeTLS ctx = (TLS.bye ctx >> TLS.contextClose ctx) -- sometimes socket was closed before 'TLS.bye'+    `E.catch` (\(_::E.SomeException) -> return ())   -- so we catch the 'Broken pipe' error here++-- | Convenience function for initiating an TLS connection to the given+-- @('HostName', 'PortNumber')@ combination.+--+-- This operation may throw 'TLS.TLSException' on failure.+--+connectTLS :: ClientParams         -- ^ check "Data.TLSSetting"+           -> Maybe String         -- ^ Optional certificate subject name, if set to 'Nothing'+                                   -- then we will try to verify 'HostName' as subject name+           -> N.HostName           -- ^ hostname to connect to+           -> N.PortNumber         -- ^ port number to connect to+           -> IO (Context, N.SockAddr)+connectTLS prms subname host port = do+    let subname' = maybe host id subname+        prms' = prms { TLS.clientServerIdentification = (subname', BC.pack (show port)) }+    (sock, addr) <- TCP.connectSocket host port+    E.bracketOnError (TLS.contextNew sock prms') closeTLS $ \ ctx -> do+        TLS.handshake ctx+        return (ctx, addr)++-- | Connect to server using TLS and return a 'Connection'.+--+connect :: ClientParams         -- ^ check "Data.TLSSetting"+        -> Maybe String         -- ^ Optional certificate subject name, if set to 'Nothing'+                                -- then we will try to verify 'HostName' as subject name+        -> N.HostName           -- ^ hostname to connect to+        -> N.PortNumber         -- ^ port number to connect to+        -> IO TLSConnection+connect prms subname host port = connectTLS prms subname host port >>= tLsToConnection++-- | Accept a new TLS connection from remote client with listening socket.+--+-- This operation may throw 'TLS.TLSException' on failure.+--+accept :: ServerParams              -- ^ check "Data.TLSSetting"+       -> N.Socket                  -- ^ the listening 'Socket'+       -> IO TLSConnection+accept prms sock = do+    (sock', addr) <- N.accept sock+    E.bracketOnError (TLS.contextNew sock' prms) closeTLS $ \ ctx -> do+        TLS.handshake ctx+        conn <- tLsToConnection (ctx, addr)+        return conn
+ test/AbandonedReplies.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE ScopedTypeVariables #-}+-- | Replies left unread on the connection. The server answers in order, so+-- whatever part of a reply stays unread used to be taken for the reply to the+-- next command (issue #10).+module AbandonedReplies (tests) where++import           Control.Exception   (SomeException, fromException, throwIO, try)+import           Control.Monad       (void)+import           Database.MySQL.Base+import           QueryApi+import qualified System.IO.Streams   as Stream+import           System.Timeout      (timeout)+import           Test.Tasty+import           Test.Tasty.HUnit+import           TestUser            (withTestConnection)++tests :: QueryApi -> TestTree+tests api = testGroup "replies left unread"+    [ testCase "a query interrupted by a timeout makes the connection refuse commands" $+        withTestConnection $ \c -> do+            interrupted <- timeout 20000+                (apiQuery_ api (ColumnCount 1) c "SELECT SLEEP(0.2)" >>= Stream.toList . snd)+            assertEqual "the timeout fired" Nothing interrupted+            next <- try (apiQuery_ api (ColumnCount 1) c "SELECT 'next'" >>= Stream.toList . snd)+            case next of+                Left AbandonedReply -> pure ()+                Right rows -> assertFailure ("the next query returned " ++ show rows)+    , testCase "resetStmt with rows unread refuses instead of reading a row as its reply" $+        withTestConnection $ \c -> do+            stmt <- prepareStmt c "SELECT 'row' UNION ALL SELECT 'row'"+            (_, rows) <- apiQueryStmt api (ColumnCount 1) c stmt []+            reset <- try (resetStmt c stmt)+            case reset of+                Left UnconsumedResultSet -> pure ()+                Right () -> assertFailure "resetStmt read a row packet as its reply"+            Stream.skipToEof rows+            (_, after) <- query_ c "SELECT 'next'"+            Stream.toList after >>= assertEqual "the next query's rows" [[MySQLText "next"]]+    , testCase "withTransaction rethrows the procedure's exception" $+        withTestConnection $ \c -> do+            outcome <- try (withTransaction c+                (apiQuery_ api (ColumnCount 1) c "SELECT 'unread'" >> throwIO (userError "procedure failed")))+            case outcome of+                Left (failure :: SomeException) -> assertEqual "exception"+                    (Just "user error (procedure failed)") (fmap show (fromException failure :: Maybe IOError))+                Right () -> assertFailure "no exception"+            next <- try (void (execute_ c "SELECT 1"))+            case next of+                Left AbandonedReply -> pure ()+                Right () -> assertFailure "the connection took a command inside the failed transaction"+    ]
+ test/Aeson.hs view
@@ -0,0 +1,338 @@+{-# LANGUAGE BangPatterns, CPP, OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-unused-binds #-}++module Aeson where++import Data.ByteString.Builder+  (Builder, byteString, toLazyByteString, charUtf8, word8)++import Control.Applicative (liftA2)+import Control.DeepSeq (NFData(..))+import Data.Attoparsec.ByteString.Char8 (Parser, char, endOfInput, scientific,+                                         skipSpace, string)+import Data.Bits ((.|.), shiftL)+import Data.ByteString (ByteString)+import Data.Char (chr)+import Data.Scientific (Scientific)+import Data.Text (Text)+import Data.Text.Encoding (decodeUtf8')+import Data.Vector as Vector (Vector, foldl', fromList)+import Data.Word (Word8)+import System.FilePath ((</>))+import qualified Data.Attoparsec.ByteString as A+import qualified Data.Attoparsec.Lazy as L+import qualified Data.Attoparsec.Zepto as Z+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Unsafe as B+import qualified Data.HashMap.Strict as H+import System.Directory (doesDirectoryExist)++#define BACKSLASH 92+#define CLOSE_CURLY 125+#define CLOSE_SQUARE 93+#define COMMA 44+#define DOUBLE_QUOTE 34+#define OPEN_CURLY 123+#define OPEN_SQUARE 91+#define C_0 48+#define C_9 57+#define C_A 65+#define C_F 70+#define C_a 97+#define C_f 102+#define C_n 110+#define C_t 116++pathTo :: String -> IO FilePath+pathTo wat = do+  exists <- doesDirectoryExist "bench"+  return $ if exists+           then "bench" </> wat+           else wat++data Result a = Error String+              | Success a+                deriving (Eq, Show)+++-- | A JSON \"object\" (key\/value map).+type Object = H.HashMap Text Value++-- | A JSON \"array\" (sequence).+type Array = Vector Value++-- | A JSON value represented as a Haskell value.+data Value = Object !Object+           | Array !Array+           | String !Text+           | Number !Scientific+           | Bool !Bool+           | Null+             deriving (Eq, Show)++instance NFData Value where+    rnf (Object o) = rnf o+    rnf (Array a)  = Vector.foldl' (\x y -> rnf y `seq` x) () a+    rnf (String s) = rnf s+    rnf (Number n) = rnf n+    rnf (Bool b)   = rnf b+    rnf Null       = ()++-- | Parse a top-level JSON value.  This must be either an object or+-- an array, per RFC 4627.+--+-- The conversion of a parsed value to a Haskell value is deferred+-- until the Haskell value is needed.  This may improve performance if+-- only a subset of the results of conversions are needed, but at a+-- cost in thunk allocation.+json :: Parser Value+json = json_ object_ array_++-- | Parse a top-level JSON value.  This must be either an object or+-- an array, per RFC 4627.+--+-- This is a strict version of 'json' which avoids building up thunks+-- during parsing; it performs all conversions immediately.  Prefer+-- this version if most of the JSON data needs to be accessed.+json' :: Parser Value+json' = json_ object_' array_'++json_ :: Parser Value -> Parser Value -> Parser Value+json_ obj ary = do+  w <- skipSpace *> A.satisfy (\w -> w == OPEN_CURLY || w == OPEN_SQUARE)+  if w == OPEN_CURLY+    then obj+    else ary+{-# INLINE json_ #-}++object_ :: Parser Value+object_ = {-# SCC "object_" #-} Object <$> objectValues jstring value++object_' :: Parser Value+object_' = {-# SCC "object_'" #-} do+  !vals <- objectValues jstring' value'+  return (Object vals)+ where+  jstring' = do+    !s <- jstring+    return s++objectValues :: Parser Text -> Parser Value -> Parser (H.HashMap Text Value)+objectValues str val = do+  skipSpace+  let pair = liftA2 (,) (str <* skipSpace) (char ':' *> skipSpace *> val)+  H.fromList <$> commaSeparated pair CLOSE_CURLY+{-# INLINE objectValues #-}++array_ :: Parser Value+array_ = {-# SCC "array_" #-} Array <$> arrayValues value++array_' :: Parser Value+array_' = {-# SCC "array_'" #-} do+  !vals <- arrayValues value'+  return (Array vals)++commaSeparated :: Parser a -> Word8 -> Parser [a]+commaSeparated item endByte = do+  w <- A.peekWord8'+  if w == endByte+    then A.anyWord8 >> return []+    else loop+  where+    loop = do+      v <- item <* skipSpace+      ch <- A.satisfy $ \w -> w == COMMA || w == endByte+      if ch == COMMA+        then skipSpace >> (v:) <$> loop+        else return [v]+{-# INLINE commaSeparated #-}++arrayValues :: Parser Value -> Parser (Vector Value)+arrayValues val = do+  skipSpace+  Vector.fromList <$> commaSeparated val CLOSE_SQUARE+{-# INLINE arrayValues #-}++-- | Parse any JSON value.  You should usually 'json' in preference to+-- this function, as this function relaxes the object-or-array+-- requirement of RFC 4627.+--+-- In particular, be careful in using this function if you think your+-- code might interoperate with Javascript.  A na&#xef;ve Javascript+-- library that parses JSON data using @eval@ is vulnerable to attack+-- unless the encoded data represents an object or an array.  JSON+-- implementations in other languages conform to that same restriction+-- to preserve interoperability and security.+value :: Parser Value+value = do+  w <- A.peekWord8'+  case w of+    DOUBLE_QUOTE  -> A.anyWord8 *> (String <$> jstring_)+    OPEN_CURLY    -> A.anyWord8 *> object_+    OPEN_SQUARE   -> A.anyWord8 *> array_+    C_f           -> string "false" *> pure (Bool False)+    C_t           -> string "true" *> pure (Bool True)+    C_n           -> string "null" *> pure Null+    _              | w >= 48 && w <= 57 || w == 45+                  -> Number <$> scientific+      | otherwise -> fail "not a valid json value"++-- | Strict version of 'value'. See also 'json''.+value' :: Parser Value+value' = do+  w <- A.peekWord8'+  case w of+    DOUBLE_QUOTE  -> do+                     !s <- A.anyWord8 *> jstring_+                     return (String s)+    OPEN_CURLY    -> A.anyWord8 *> object_'+    OPEN_SQUARE   -> A.anyWord8 *> array_'+    C_f           -> string "false" *> pure (Bool False)+    C_t           -> string "true" *> pure (Bool True)+    C_n           -> string "null" *> pure Null+    _              | w >= 48 && w <= 57 || w == 45+                  -> do+                     !n <- scientific+                     return (Number n)+      | otherwise -> fail "not a valid json value"++-- | Parse a quoted JSON string.+jstring :: Parser Text+jstring = A.word8 DOUBLE_QUOTE *> jstring_++-- | Parse a string without a leading quote.+jstring_ :: Parser Text+jstring_ = {-# SCC "jstring_" #-} do+  s <- A.scan False $ \s c -> if s then Just False+                                   else if c == DOUBLE_QUOTE+                                        then Nothing+                                        else Just (c == BACKSLASH)+  _ <- A.word8 DOUBLE_QUOTE+  s1 <- if BACKSLASH `B.elem` s+        then case Z.parse unescape s of+            Right r  -> return r+            Left err -> fail err+         else return s++  case decodeUtf8' s1 of+      Right r  -> return r+      Left err -> fail $ show err++{-# INLINE jstring_ #-}++unescape :: Z.Parser ByteString+unescape = toByteString <$> go mempty where+  go acc = do+    h <- Z.takeWhile (/=BACKSLASH)+    let rest = do+          start <- Z.take 2+          let !slash = B.unsafeHead start+              !t = B.unsafeIndex start 1+              escape = case B.findIndex (==t) "\"\\/ntbrfu" of+                         Just i -> i+                         _      -> 255+          if slash /= BACKSLASH || escape == 255+            then fail "invalid JSON escape sequence"+            else do+            let cont m = go (acc `mappend` byteString h `mappend` m)+                {-# INLINE cont #-}+            if t /= 117 -- 'u'+              then cont (word8 (B.unsafeIndex mapping escape))+              else do+                   a <- hexQuad+                   if a < 0xd800 || a > 0xdfff+                     then cont (charUtf8 (chr a))+                     else do+                       b <- Z.string "\\u" *> hexQuad+                       if a <= 0xdbff && b >= 0xdc00 && b <= 0xdfff+                         then let !c = ((a - 0xd800) `shiftL` 10) ++                                       (b - 0xdc00) + 0x10000+                              in cont (charUtf8 (chr c))+                         else fail "invalid UTF-16 surrogates"+    done <- Z.atEnd+    if done+      then return (acc `mappend` byteString h)+      else rest+  mapping = "\"\\/\n\t\b\r\f"++hexQuad :: Z.Parser Int+hexQuad = do+  s <- Z.take 4+  let hex n | w >= C_0 && w <= C_9 = w - C_0+            | w >= C_a && w <= C_f = w - 87+            | w >= C_A && w <= C_F = w - 55+            | otherwise          = 255+        where w = fromIntegral $ B.unsafeIndex s n+      a = hex 0; b = hex 1; c = hex 2; d = hex 3+  if (a .|. b .|. c .|. d) /= 255+    then return $! d .|. (c `shiftL` 4) .|. (b `shiftL` 8) .|. (a `shiftL` 12)+    else fail "invalid hex escape"++decodeWith :: Parser Value -> (Value -> Result a) -> L.ByteString -> Maybe a+decodeWith p to s =+    case L.parse p s of+      L.Done _ v -> case to v of+                      Success a -> Just a+                      _         -> Nothing+      _          -> Nothing+{-# INLINE decodeWith #-}++decodeStrictWith :: Parser Value -> (Value -> Result a) -> B.ByteString+                 -> Maybe a+decodeStrictWith p to s =+    case either Error to (A.parseOnly p s) of+      Success a -> Just a+      Error _ -> Nothing+{-# INLINE decodeStrictWith #-}++eitherDecodeWith :: Parser Value -> (Value -> Result a) -> L.ByteString+                 -> Either String a+eitherDecodeWith p to s =+    case L.parse p s of+      L.Done _ v -> case to v of+                      Success a -> Right a+                      Error msg -> Left msg+      L.Fail _ _ msg -> Left msg+{-# INLINE eitherDecodeWith #-}++eitherDecodeStrictWith :: Parser Value -> (Value -> Result a) -> B.ByteString+                       -> Either String a+eitherDecodeStrictWith p to s =+    case either Error to (A.parseOnly p s) of+      Success a -> Right a+      Error msg -> Left msg+{-# INLINE eitherDecodeStrictWith #-}++-- $lazy+--+-- The 'json' and 'value' parsers decouple identification from+-- conversion.  Identification occurs immediately (so that an invalid+-- JSON document can be rejected as early as possible), but conversion+-- to a Haskell value is deferred until that value is needed.+--+-- This decoupling can be time-efficient if only a smallish subset of+-- elements in a JSON value need to be inspected, since the cost of+-- conversion is zero for uninspected elements.  The trade off is an+-- increase in memory usage, due to allocation of thunks for values+-- that have not yet been converted.++-- $strict+--+-- The 'json'' and 'value'' parsers combine identification with+-- conversion.  They consume more CPU cycles up front, but have a+-- smaller memory footprint.++-- | Parse a top-level JSON value followed by optional whitespace and+-- end-of-input.  See also: 'json'.+jsonEOF :: Parser Value+jsonEOF = json <* skipSpace <* endOfInput++-- | Parse a top-level JSON value followed by optional whitespace and+-- end-of-input.  See also: 'json''.+jsonEOF' :: Parser Value+jsonEOF' = json' <* skipSpace <* endOfInput++toByteString :: Builder -> ByteString+toByteString = L.toStrict . toLazyByteString+{-# INLINE toByteString #-}
+ test/AesonBP.hs view
@@ -0,0 +1,284 @@+{-# LANGUAGE BangPatterns, CPP, OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-unused-binds #-}++module AesonBP where++import Data.ByteString.Builder+  (Builder, byteString, toLazyByteString, charUtf8, word8)++import Control.Applicative (liftA2)+import Data.Bits ((.|.), shiftL)+import Data.ByteString (ByteString)+import Data.Char (chr)+import Data.Text (Text)+import Data.Text.Encoding (decodeUtf8')+import Data.Vector as Vector (Vector, fromList)+import Data.Word (Word8)+import System.FilePath ((</>))+import qualified Data.Attoparsec.Zepto as Z+import Data.Binary.Get (Get)+import qualified Data.Binary.Parser as BP+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Unsafe as B+import qualified Data.HashMap.Strict as H+import System.Directory (doesDirectoryExist)+import Aeson (Value(..))++#define BACKSLASH 92+#define CLOSE_CURLY 125+#define CLOSE_SQUARE 93+#define COMMA 44+#define COLON 58+#define DOUBLE_QUOTE 34+#define OPEN_CURLY 123+#define OPEN_SQUARE 91+#define C_0 48+#define C_9 57+#define C_A 65+#define C_F 70+#define C_a 97+#define C_f 102+#define C_n 110+#define C_t 116++pathTo :: String -> IO FilePath+pathTo wat = do+  exists <- doesDirectoryExist "bench"+  return $ if exists+           then "bench" </> wat+           else wat++data Result a = Error String+              | Success a+                deriving (Eq, Show)+++-- | A JSON \"object\" (key\/value map).+type Object = H.HashMap Text Value++-- | A JSON \"array\" (sequence).+type Array = Vector Value++-- | Parse a top-level JSON value.  This must be either an object or+-- an array, per RFC 4627.+--+-- The conversion of a parsed value to a Haskell value is deferred+-- until the Haskell value is needed.  This may improve performance if+-- only a subset of the results of conversions are needed, but at a+-- cost in thunk allocation.+json :: Get Value+json = json_ object_ array_++-- | Parse a top-level JSON value.  This must be either an object or+-- an array, per RFC 4627.+--+-- This is a strict version of 'json' which avoids building up thunks+-- during parsing; it performs all conversions immediately.  Prefer+-- this version if most of the JSON data needs to be accessed.+json' :: Get Value+json' = json_ object_' array_'++json_ :: Get Value -> Get Value -> Get Value+json_ obj ary = do+  w <- BP.skipSpaces *> BP.satisfy (\w -> w == OPEN_CURLY || w == OPEN_SQUARE)+  if w == OPEN_CURLY+    then obj+    else ary+{-# INLINE json_ #-}++object_ :: Get Value+object_ = {-# SCC "object_" #-} Object <$> objectValues jstring value++object_' :: Get Value+object_' = {-# SCC "object_'" #-} do+  !vals <- objectValues jstring' value'+  return (Object vals)+ where+  jstring' = do+    !s <- jstring+    return s++objectValues :: Get Text -> Get Value -> Get (H.HashMap Text Value)+objectValues str val = do+  BP.skipSpaces+  let pair = liftA2 (,) (str <* BP.skipSpaces) (BP.word8 COLON *> BP.skipSpaces *> val)+  H.fromList <$> commaSeparated pair CLOSE_CURLY+{-# INLINE objectValues #-}++array_ :: Get Value+array_ = {-# SCC "array_" #-} Array <$> arrayValues value++array_' :: Get Value+array_' = {-# SCC "array_'" #-} do+  !vals <- arrayValues value'+  return (Array vals)++commaSeparated :: Get a -> Word8 -> Get [a]+commaSeparated item endByte = do+  w <- BP.peek+  if w == endByte+    then BP.skipN 1 >> return []+    else loop+  where+    loop = do+      v <- item <* BP.skipSpaces+      ch <- BP.satisfy $ \w -> w == COMMA || w == endByte+      if ch == COMMA+        then BP.skipSpaces >> (v:) <$> loop+        else return [v]+{-# INLINE commaSeparated #-}++arrayValues :: Get Value -> Get (Vector Value)+arrayValues val = do+  BP.skipSpaces+  Vector.fromList <$> commaSeparated val CLOSE_SQUARE+{-# INLINE arrayValues #-}++-- | Parse any JSON value.  You should usually 'json' in preference to+-- this function, as this function relaxes the object-or-array+-- requirement of RFC 4627.+--+-- In particular, be careful in using this function if you think your+-- code might interoperate with Javascript.  A na&#xef;ve Javascript+-- library that parses JSON data using @eval@ is vulnerable to attack+-- unless the encoded data represents an object or an array.  JSON+-- implementations in other languages conform to that same restriction+-- to preserve interoperability and security.+value :: Get Value+value = do+  w <- BP.peek+  case w of+    DOUBLE_QUOTE  -> BP.skipN 1 *> (String <$> jstring_)+    OPEN_CURLY    -> BP.skipN 1 *> object_+    OPEN_SQUARE   -> BP.skipN 1 *> array_+    C_f           -> BP.string "false" *> pure (Bool False)+    C_t           -> BP.string "true" *> pure (Bool True)+    C_n           -> BP.string "null" *> pure Null+    _              | w >= 48 && w <= 57 || w == 45+                  -> Number <$> BP.scientific+      | otherwise -> fail "not a valid json value"++-- | Strict version of 'value'. See also 'json''.+value' :: Get Value+value' = do+  w <- BP.peek+  case w of+    DOUBLE_QUOTE  -> do+                     !s <- BP.skipN 1 *> jstring_+                     return (String s)+    OPEN_CURLY    -> BP.skipN 1 *> object_'+    OPEN_SQUARE   -> BP.skipN 1 *> array_'+    C_f           -> BP.string "false" *> pure (Bool False)+    C_t           -> BP.string "true" *> pure (Bool True)+    C_n           -> BP.string "null" *> pure Null+    _              | w >= 48 && w <= 57 || w == 45+                  -> do+                     !n <- BP.scientific+                     return (Number n)+      | otherwise -> fail "not a valid json value"++-- | Parse a quoted JSON string.+jstring :: Get Text+jstring = BP.word8 DOUBLE_QUOTE *> jstring_++-- | Parse a string without a leading quote.+jstring_ :: Get Text+jstring_ = {-# SCC "jstring_" #-} do+  s <- BP.scan False $ \s c -> if s then Just False+                                   else if c == DOUBLE_QUOTE+                                        then Nothing+                                        else Just (c == BACKSLASH)+  BP.word8 DOUBLE_QUOTE+  s1 <- if BACKSLASH `B.elem` s+        then case Z.parse unescape s of+            Right r  -> return r+            Left err -> fail err+         else return s++  case decodeUtf8' s1 of+      Right r  -> return r+      Left err -> fail $ show err++{-# INLINE jstring_ #-}++unescape :: Z.Parser ByteString+unescape = toByteString <$> go mempty where+  go acc = do+    h <- Z.takeWhile (/=BACKSLASH)+    let rest = do+          start <- Z.take 2+          let !slash = B.unsafeHead start+              !t = B.unsafeIndex start 1+              escape = case B.findIndex (==t) "\"\\/ntbrfu" of+                         Just i -> i+                         _      -> 255+          if slash /= BACKSLASH || escape == 255+            then fail "invalid JSON escape sequence"+            else do+            let cont m = go (acc `mappend` byteString h `mappend` m)+                {-# INLINE cont #-}+            if t /= 117 -- 'u'+              then cont (word8 (B.unsafeIndex mapping escape))+              else do+                   a <- hexQuad+                   if a < 0xd800 || a > 0xdfff+                     then cont (charUtf8 (chr a))+                     else do+                       b <- Z.string "\\u" *> hexQuad+                       if a <= 0xdbff && b >= 0xdc00 && b <= 0xdfff+                         then let !c = ((a - 0xd800) `shiftL` 10) ++                                       (b - 0xdc00) + 0x10000+                              in cont (charUtf8 (chr c))+                         else fail "invalid UTF-16 surrogates"+    done <- Z.atEnd+    if done+      then return (acc `mappend` byteString h)+      else rest+  mapping = "\"\\/\n\t\b\r\f"++hexQuad :: Z.Parser Int+hexQuad = do+  s <- Z.take 4+  let hex n | w >= C_0 && w <= C_9 = w - C_0+            | w >= C_a && w <= C_f = w - 87+            | w >= C_A && w <= C_F = w - 55+            | otherwise          = 255+        where w = fromIntegral $ B.unsafeIndex s n+      a = hex 0; b = hex 1; c = hex 2; d = hex 3+  if (a .|. b .|. c .|. d) /= 255+    then return $! d .|. (c `shiftL` 4) .|. (b `shiftL` 8) .|. (a `shiftL` 12)+    else fail "invalid hex escape"++-- $lazy+--+-- The 'json' and 'value' parsers decouple identification from+-- conversion.  Identification occurs immediately (so that an invalid+-- JSON document can be rejected as early as possible), but conversion+-- to a Haskell value is deferred until that value is needed.+--+-- This decoupling can be time-efficient if only a smallish subset of+-- elements in a JSON value need to be inspected, since the cost of+-- conversion is zero for uninspected elements.  The trade off is an+-- increase in memory usage, due to allocation of thunks for values+-- that have not yet been converted.++-- $strict+--+-- The 'json'' and 'value'' parsers combine identification with+-- conversion.  They consume more CPU cycles up front, but have a+-- smaller memory footprint.++-- | Parse a top-level JSON value followed by optional whitespace and+-- end-of-input.  See also: 'json'.+jsonEOF :: Get Value+jsonEOF = json <* BP.skipSpaces++-- | Parse a top-level JSON value followed by optional whitespace and+-- end-of-input.  See also: 'json''.+jsonEOF' :: Get Value+jsonEOF' = json' <* BP.skipSpaces++toByteString :: Builder -> ByteString+toByteString = L.toStrict . toLazyByteString+{-# INLINE toByteString #-}
+ test/BinLogChecksum.hs view
@@ -0,0 +1,95 @@+-- | The CRC32 checksum that ends each binlog event when binlog_checksum is+-- CRC32, which the decoder used to drop unchecked, decoding a corrupted event+-- as if it were intact.+module BinLogChecksum (tests) where++import           Control.Exception                    (try)+import           Data.Bits                            (xor)+import qualified Data.ByteString                      as B+import qualified Data.ByteString.Base16               as Base16+import qualified Data.ByteString.Lazy                 as L+import           Data.Word                            (Word32)+import           Database.MySQL.BinLog+import           Database.MySQL.BinLogProtocol.CRC32  (crc32)+import           Database.MySQL.Protocol.Packet+import           Test.Tasty+import           Test.Tasty.HUnit++tests :: TestTree+tests = testGroup "binlog checksums"+    [ testCase "CRC32 matches the IEEE/zlib check values" $ do+        assertChecksum "check string" 0xCBF43926 (crc32 "123456789")+        assertChecksum "empty" 0x00000000 (crc32 "")+        assertChecksum "pangram" 0x414FA339 (crc32 "The quick brown fox jumps over the lazy dog")+        assertChecksum "every byte value" 0x29058C73 (crc32 (L.pack [0 .. 255]))+    , testCase "an intact event decodes" $ do+        xid <- mysqlXidEvent+        event <- decodeBinLogPacket True False fileName (eventPacket xid)+        assertEqual "body" (L.fromStrict (B.take 8 (B.drop 19 xid))) (blBody event)+    , testCase "an event with one byte flipped raises BinLogChecksumMismatch" $ do+        xid <- mysqlXidEvent+        decoded <- try (decodeBinLogPacket True False fileName (eventPacket (flipByte 20 xid)))+        assertEqual "mismatch"+            (Left (BinLogChecksumMismatch BINLOG_XID_EVENT fileName 3125+                (BinLogChecksum 0x73C5F461) (BinLogChecksum 0xD5B2FFD5)))+            (blEventType <$> decoded)+    , testCase "a format description event with the binlog-in-use flag set decodes" $ do+        description <- mysqlFormatDescription+        event <- decodeBinLogPacket True False fileName (eventPacket description)+        assertEqual "event type" BINLOG_FORMAT_DESCRIPTION_EVENT (blEventType event)+    , testCase "a corrupted format description event raises BinLogChecksumMismatch" $ do+        description <- mysqlFormatDescription+        decoded <- try (decodeBinLogPacket True False fileName (eventPacket (flipByte 23 description)))+        assertEqual "mismatch"+            (Left (BinLogChecksumMismatch BINLOG_FORMAT_DESCRIPTION_EVENT fileName 126+                (BinLogChecksum 0xBEB97E09) (BinLogChecksum 0xD8DB7FA5)))+            (blEventType <$> decoded)+    , testCase "a format description event naming no checksum algorithm is not checked" $ do+        description <- mysqlFormatDescription+        let algorithmOff = B.length description - 5+        event <- decodeBinLogPacket True False fileName (eventPacket (flipByte algorithmOff description))+        assertEqual "event type" BINLOG_FORMAT_DESCRIPTION_EVENT (blEventType event)+    , testCase "the semi-sync header is not part of the checksummed event" $ do+        xid <- mysqlXidEvent+        let body = L.fromStrict ("\x00\xEF\x01" <> xid)+        event <- decodeBinLogPacket True True fileName (Packet (L.length body) 1 body)+        assertEqual "semi-sync ack" True (blSemiAck event)+    , testCase "events of a dump without checksums are not checked" $ do+        xid <- mysqlXidEvent+        event <- decodeBinLogPacket False False fileName (eventPacket (flipByte 20 xid))+        assertEqual "event type" BINLOG_XID_EVENT (blEventType event)+    ]++assertChecksum :: String -> Word32 -> Word32 -> Assertion+assertChecksum = assertEqual++fileName :: B.ByteString+fileName = "mysql-bin.000001"++-- | An event as COM_BINLOG_DUMP streams it, after the OK byte.+eventPacket :: B.ByteString -> Packet+eventPacket event = Packet (L.length body) 1 body+  where+    body = L.fromStrict (B.cons 0x00 event)++flipByte :: Int -> B.ByteString -> B.ByteString+flipByte index bytes =+    B.take index bytes <> B.map (`xor` 0x01) (B.take 1 (B.drop index bytes)) <> B.drop (index + 1) bytes++-- | An XID event that MySQL 8.0.45 wrote, checksum included.+mysqlXidEvent :: IO B.ByteString+mysqlXidEvent = fromHex "e8baf46a10010000001f000000350c00000000250000000000000061f4c573"++-- | The format description event that MySQL 8.0.45 wrote at the start of its+-- binlog file, with LOG_EVENT_BINLOG_IN_USE_F set as the open file holds it.+mysqlFormatDescription :: IO B.ByteString+mysqlFormatDescription = fromHex+    "e5baf46a0f010000007a0000007e00000001000400382e302e34350000000000000000\+    \000000000000000000000000000000000000000000000000000000000000000000000000\+    \e5baf46a13000d0008000000000400040000006200041a08000000080808020000000a0a\+    \0a2a2a001234000a280001097eb9be"++fromHex :: B.ByteString -> IO B.ByteString+fromHex hex = case Base16.decode hex of+    Left failure -> assertFailure ("a test event is not hex: " ++ failure)+    Right bytes -> pure bytes
+ test/BinLogEventCodes.hs view
@@ -0,0 +1,59 @@+-- | Binlog events of a type this library has no constructor for, such as+-- MySQL 8's compressed TRANSACTION_PAYLOAD_EVENT (40) or MariaDB's GTID+-- event (162), whose type code used to crash the decoder through toEnum.+module BinLogEventCodes (tests) where++import           Control.Exception              (try)+import qualified Data.ByteString.Lazy           as L+import           Data.Binary.Put                (runPut, putWord8, putWord16le, putWord32le, putLazyByteString)+import           Data.IORef                     (newIORef)+import           Data.Word                      (Word16, Word8)+import           Database.MySQL.BinLog+import           Database.MySQL.Protocol.Packet+import qualified System.IO.Streams              as Stream+import           Test.Tasty+import           Test.Tasty.HUnit++tests :: TestTree+tests = testGroup "binlog event codes"+    [ testCase "an event of an unknown type keeps its type code" $ do+        event <- getFromPacket (getBinLogPacket False False) (eventPacket 162 notIgnorable)+        assertEqual "type code" 162 (binLogEventTypeCode (blEventType event))+    , testCase "the row decoder refuses an unknown event not marked ignorable" $ do+        rows <- decodeEvents [eventPacket 40 notIgnorable]+        decoded <- try (Stream.read rows)+        case decoded of+            Left (UnsupportedEventType code _) -> assertEqual "type code" 40 code+            Left other -> assertFailure ("raised " <> show other)+            Right event -> assertFailure ("decoded " <> show event)+    , testCase "the row decoder skips an unknown event marked ignorable" $ do+        rows <- decodeEvents [eventPacket 40 ignorable]+        decoded <- Stream.read rows+        assertEqual "row events" Nothing decoded+    ]++decodeEvents :: [Packet] -> IO (Stream.InputStream RowBinLogEvent)+decodeEvents packets = do+    events <- traverse (getFromPacket (getBinLogPacket False False)) packets+    fileName <- newIORef "mysql-bin.000001"+    eventStream <- Stream.fromList events+    decodeRowBinLogEvent (FormatDescription 4 "" 0 "", fileName, eventStream)++-- | A binlog event packet as COM_BINLOG_DUMP streams it, without checksum.+eventPacket :: Word8 -> Word16 -> Packet+eventPacket typeCode flags = Packet (L.length body) 1 body+  where+    eventBody = "\x01\x02\x03"+    body = runPut $ do+        putWord8 0x00                      -- OK marker+        putWord32le 1700000000             -- timestamp+        putWord8 typeCode+        putWord32le 1                      -- server id+        putWord32le (19 + 3)               -- event size: header and body+        putWord32le 4096                   -- next position+        putWord16le flags+        putLazyByteString eventBody++notIgnorable, ignorable :: Word16+notIgnorable = 0x0000+ignorable = 0x0080
+ test/BinLogRowEvents.hs view
@@ -0,0 +1,119 @@+{-# LANGUAGE ScopedTypeVariables #-}+-- | Row events the binlog decoder used to drop: those after the first one of+-- a statement, which splits its rows over events of about 8 KiB, and those+-- of a statement that maps several tables before its first rows event.+module BinLogRowEvents (tests, compressedTransactionTests) where++import           Control.Concurrent  (forkIO)+import           Control.Exception   (SomeException, fromException, try)+import           Control.Monad       (forM_, void)+import qualified Data.ByteString.Lazy.Char8 as LC+import           Data.IORef          (IORef, modifyIORef', newIORef, readIORef)+import           Data.List           (intercalate)+import           Database.MySQL.Base+import           Database.MySQL.BinLog+import qualified System.IO.Streams   as Stream+import           System.Timeout      (timeout)+import           Test.Tasty+import           Test.Tasty.HUnit+import           TestUser            (testConnectInfo)++tests :: TestTree+tests = testGroup "binlog row events"+    [ testCase "every rows event of a statement that spans several" $ do+            c <- dumpConnection+            void (execute_ c "DROP TABLE IF EXISTS binlog_split_rows")+            void (execute_ c "CREATE TABLE binlog_split_rows (id INT, filler VARCHAR(1000))")+            rowEvents <- rowEventsFrom c 1010 $ \producer ->+                execute_ producer (Query (LC.pack ("INSERT INTO binlog_split_rows VALUES "+                    ++ intercalate ", " (map splitRow [1 .. splitRowCount]))))+            written <- countUntil splitRowCount rowEvents writtenRows+            assertEqual "rows decoded" (Just splitRowCount) written+    , testCase "every table of a statement that maps two before its rows" $ do+            c <- dumpConnection+            forM_ ["binlog_left", "binlog_right" :: LC.ByteString] $ \table -> do+                void (execute_ c (Query ("DROP TABLE IF EXISTS " <> table)))+                void (execute_ c (Query ("CREATE TABLE " <> table <> " (id INT, v INT)")))+                void (execute_ c (Query ("INSERT INTO " <> table <> " VALUES (1, 0)")))+            rowEvents <- rowEventsFrom c 1011 $ \producer ->+                execute_ producer "UPDATE binlog_left, binlog_right \+                                  \SET binlog_left.v = 1, binlog_right.v = 1 \+                                  \WHERE binlog_left.id = binlog_right.id"+            updated <- countUntil 2 rowEvents updatedRows+            assertEqual "update rows decoded across both tables" (Just 2) updated+    ]++-- | MySQL 8 only: a compressed transaction is one TRANSACTION_PAYLOAD_EVENT+-- holding its rows, which the decoder cannot read.+compressedTransactionTests :: TestTree+compressedTransactionTests = testCase "a compressed transaction stops the row decoder" $ do+        c <- dumpConnection+        void (execute_ c "DROP TABLE IF EXISTS binlog_compressed")+        void (execute_ c "CREATE TABLE binlog_compressed (id INT)")+        rowEvents <- rowEventsFrom c 1012 $ \producer -> do+            void (execute_ producer "SET SESSION binlog_transaction_compression = ON")+            execute_ producer "INSERT INTO binlog_compressed VALUES (1)"+        decoded <- timeout tenSeconds (try (Stream.read rowEvents))+        case decoded of+            Just (Left (failure :: SomeException)) -> case fromException failure of+                Just (UnsupportedEventType code _) -> assertEqual "event type" 40 code+                _ -> assertFailure ("raised " ++ show failure)+            Just (Right event) -> assertFailure ("decoded " ++ show event)+            Nothing -> assertFailure "no event within 10 s"++-- | A connection for 'dumpBinLog', left open: once dumping, the server stops+-- reading commands from it, so 'close' would wait for its next event (issue #35).+dumpConnection :: IO MySQLConn+dumpConnection = connect testConnectInfo++-- | The row events from the current end of the binlog on, while @produce@+-- runs on a second connection.+rowEventsFrom :: MySQLConn -> SlaveID -> (MySQLConn -> IO OK) -> IO (Stream.InputStream RowBinLogEvent)+rowEventsFrom c slaveId produce = do+    Just tracker <- getLastBinLogTracker c+    dump <- dumpBinLog c slaveId tracker False+    rowEvents <- decodeRowBinLogEvent dump+    void (forkIO (connect testConnectInfo >>= \producer -> produce producer >> close producer))+    pure rowEvents++-- | Reads events until the counted rows reach @target@; Nothing when they+-- do not within 10 s, as when events are dropped.+countUntil :: Int -> Stream.InputStream RowBinLogEvent -> (RowBinLogEvent -> Int) -> IO (Maybe Int)+countUntil target rowEvents rowsOf = do+    counted <- newIORef 0+    reached <- timeout tenSeconds (readUntil target rowEvents rowsOf counted)+    case reached of+        Just () -> Just <$> readIORef counted+        Nothing -> pure Nothing++readUntil :: Int -> Stream.InputStream RowBinLogEvent -> (RowBinLogEvent -> Int) -> IORef Int -> IO ()+readUntil target rowEvents rowsOf counted = do+    event <- Stream.read rowEvents+    forM_ event (\e -> modifyIORef' counted (+ rowsOf e))+    total <- readIORef counted+    if total >= target then pure () else readUntil target rowEvents rowsOf counted++writtenRows :: RowBinLogEvent -> Int+writtenRows event = case event of+    RowWriteEvent _ _ table rows -> if tmTableName table == "binlog_split_rows" then length (writeRowData rows) else 0+    RowUpdateEvent {} -> 0+    RowDeleteEvent {} -> 0+    RowQueryEvent {} -> 0++updatedRows :: RowBinLogEvent -> Int+updatedRows event = case event of+    RowUpdateEvent _ _ _ rows -> length (updateRowData rows)+    RowWriteEvent {} -> 0+    RowDeleteEvent {} -> 0+    RowQueryEvent {} -> 0++-- | Enough 1000-byte rows for several rows events of binlog_row_event_max_size+-- (8 KiB by default).+splitRowCount :: Int+splitRowCount = 200++splitRow :: Int -> String+splitRow n = "(" ++ show n ++ ", REPEAT('x', 1000))"++tenSeconds :: Int+tenSeconds = 10000000
test/BinaryRow.hs view
@@ -9,17 +9,22 @@ import           Data.Time.Calendar  (fromGregorian) import           Data.Time.LocalTime (LocalTime (..), TimeOfDay (..)) import           Database.MySQL.Base+import           QueryApi import qualified System.IO.Streams   as Stream import           Test.Tasty.HUnit import qualified Data.Text as T import qualified Data.ByteString as B import qualified Data.Vector as V -tests :: MySQLConn -> Assertion-tests c = do+-- | The columns of the @test@ table.+testColumns :: ColumnCount+testColumns = ColumnCount 30++tests :: QueryApi -> MySQLConn -> Assertion+tests api c = do     selStmt <- prepareStmt c "SELECT * FROM test" -    (f, is) <- queryStmt c selStmt []+    (f, is) <- apiQueryStmt api testColumns c selStmt []     assertEqual "decode Field types" (columnType <$> f)         [ mySQLTypeLong         , mySQLTypeBit@@ -122,7 +127,7 @@                 \__enum       = 'foo'                                  ,\                 \__set        = 'foo,bar' WHERE __id=0" -    (_, is) <- queryStmt c selStmt []+    (_, is) <- apiQueryStmt api testColumns c selStmt []     Just v <- Stream.read is      assertEqual "decode binary protocol" v@@ -158,7 +163,7 @@         , MySQLText "foo,bar"]     Stream.skipToEof is -    (_, is') <- queryStmtVector c selStmt []+    (_, is') <- apiQueryStmtVector api testColumns c selStmt []     Just v' <- Stream.read is'     Stream.skipToEof is' @@ -231,7 +236,7 @@   -    (_, is) <- queryStmt c selStmt []+    (_, is) <- apiQueryStmt api testColumns c selStmt []     Just v <- Stream.read is      assertEqual "roundtrip binary protocol" v@@ -274,7 +279,7 @@         \__double     = null         ,\         \__text = null WHERE __id=0" -    (_, is) <- queryStmt c selStmt []+    (_, is) <- apiQueryStmt api testColumns c selStmt []     Just v <- Stream.read is      assertEqual "decode binary protocol with null" v@@ -318,7 +323,7 @@         \__timestamp = ? WHERE __id=0"     executeStmt c updStmt1 [MySQLNull, MySQLNull, MySQLNull] -    (_, is) <- queryStmt c selStmt []+    (_, is) <- apiQueryStmt api testColumns c selStmt []     Just v <- Stream.read is      assertEqual "roundtrip binary protocol with null" v@@ -360,7 +365,7 @@         \__year       = 0  WHERE __id=0"      selStmt2 <- prepareStmt c "SELECT __time, __year FROM test"-    (_, is) <- queryStmt c selStmt2 []+    (_, is) <- apiQueryStmt api (ColumnCount 2) c selStmt2 []     Just v <- Stream.read is      assertEqual "decode binary protocol 2" v@@ -376,7 +381,7 @@      executeStmt c updStmt2 [ MySQLTime 0 (TimeOfDay 00 00 00), MySQLYear 2055] -    (_, is) <- queryStmt c selStmt2 []+    (_, is) <- apiQueryStmt api (ColumnCount 2) c selStmt2 []     Just v <- Stream.read is     assertEqual "roundtrip binary protocol 2" v             [ MySQLTime 0 (TimeOfDay 00 00 00)@@ -390,7 +395,7 @@         \__blob       = ''  WHERE __id=0"      selStmt3 <- prepareStmt c "SELECT __text, __blob FROM test"-    (_, is) <- queryStmt c selStmt3 []+    (_, is) <- apiQueryStmt api (ColumnCount 2) c selStmt3 []     Just v <- Stream.read is      assertEqual "decode binary protocol 3" v@@ -409,7 +414,7 @@         , MySQLBytes (B.replicate 1000000 64)         ] -    (_, is) <- queryStmt c selStmt3 []+    (_, is) <- apiQueryStmt api (ColumnCount 2) c selStmt3 []     Just v <- Stream.read is     assertEqual "roundtrip binary protocol 3" v         [ MySQLText (T.replicate 100000 "xyz")
test/BinaryRowNew.hs view
@@ -7,14 +7,19 @@ import           Data.Time.Calendar  (fromGregorian) import           Data.Time.LocalTime (LocalTime (..), TimeOfDay (..)) import           Database.MySQL.Base+import           QueryApi import qualified System.IO.Streams   as Stream import           Test.Tasty.HUnit -tests :: MySQLConn -> Assertion-tests c = do+-- | The columns of the @test_new@ table.+testNewColumns :: ColumnCount+testNewColumns = ColumnCount 4++tests :: QueryApi -> MySQLConn -> Assertion+tests api c = do     selStmt <- prepareStmt c "SELECT * FROM test_new" -    (f, is) <- queryStmt c selStmt []+    (f, is) <- apiQueryStmt api testNewColumns c selStmt []     assertEqual "decode Field types" (columnType <$> f)         [ mySQLTypeLong         , mySQLTypeDateTime@@ -37,7 +42,7 @@                 \__timestamp  = '2016-08-08 17:25:59.1234'                ,\                 \__time       = '-199:59:59.123456' WHERE __id=0" -    (_, is) <- queryStmt c selStmt []+    (_, is) <- apiQueryStmt api testNewColumns c selStmt []     Just v <- Stream.read is      assertEqual "decode binary protocol" v@@ -54,7 +59,7 @@                 \__timestamp  = '2016-08-08 17:25:59.12'                ,\                 \__time       = '199:59:59.123' WHERE __id=0" -    (_, is) <- queryStmt c selStmt []+    (_, is) <- apiQueryStmt api testNewColumns c selStmt []     Just v <- Stream.read is      assertEqual "decode binary protocol 2" v@@ -79,7 +84,7 @@                 ]  -    (_, is) <- queryStmt c selStmt []+    (_, is) <- apiQueryStmt api testNewColumns c selStmt []     Just v <- Stream.read is      assertEqual "roundtrip binary protocol" v@@ -98,7 +103,7 @@                 ]  -    (_, is) <- queryStmt c selStmt []+    (_, is) <- apiQueryStmt api testNewColumns c selStmt []     Just v <- Stream.read is      assertEqual "roundtrip binary protocol 2" v
+ test/BoundsCheck.hs view
@@ -0,0 +1,175 @@+module BoundsCheck (tests) where++import qualified Data.ByteString             as B+import qualified Data.ByteString.Lazy        as L+import           Data.Binary.Put             (runPut, putWord8)+import           Data.Binary.Parser          (parseOnly)+import           Test.Tasty+import           Test.Tasty.HUnit++import           Database.MySQL.Protocol.ColumnDef+import           Data.Bifunctor              (first)+import qualified Data.Vector                 as V+import qualified Database.MySQL.Decoder      as Decode+import           Database.MySQL.Protocol.MySQLValue (ColumnKind, ColumnNumber (..),+                                                     MySQLValue, columnKind,+                                                     getTextField)+import           TypedValue                  (typedDecoder)+import           Database.MySQL.Protocol.Packet (putLenEncInt)+import           Database.MySQL.BinLogProtocol.BinLogEvent+                    (FormatDescription(..), eventHeaderLen, BinLogEventType(..))+import           Database.MySQL.BinLogProtocol.BinLogValue (getBinLogField)+import           Database.MySQL.BinLogProtocol.BinLogMeta (BinLogMeta(..))++tests :: TestTree+tests = testGroup "bounds-check"+    [ testGroup "text-protocol" (map textProtocolTests textFieldApis)+    , testGroup "binlog-value" binlogValueTests+    , testGroup "binlog-event" binlogEventTests+    ]++--------------------------------------------------------------------------------+-- Helpers++-- | Build a ColumnDef with the given field type and default flags.+mkColumnDef :: FieldType -> ColumnDef+mkColumnDef ft = ColumnDef+    { columnDB        = ""+    , columnTable     = ""+    , columnOrigTable = ""+    , columnName      = ""+    , columnOrigName  = ""+    , columnCharSet   = 63 -- binary+    , columnLength    = 0+    , columnType      = ft+    , columnFlags     = 0+    , columnDecimals  = 0+    }++-- | Encode a length-prefixed ByteString as the MySQL wire protocol does.+lenEncoded :: B.ByteString -> B.ByteString+lenEncoded payload = L.toStrict . runPut $ do+    putLenEncInt (B.length payload)+    mapM_ putWord8 (B.unpack payload)++-- | Encode a length-prefixed field, then run 'getTextField' on it.+parseLenEncField :: ColumnDef -> B.ByteString -> Either String MySQLValue+parseLenEncField cd payload = parseOnly (getTextField cd) (lenEncoded payload)++-- | A text-protocol field decoded through the old 'getTextField' or one of the+-- paths of "Database.MySQL.Decoder"; every text-protocol case runs through each.+data TextFieldApi = TextFieldApi String (ColumnDef -> B.ByteString -> Either String MySQLValue)++textFieldApis :: [TextFieldApi]+textFieldApis =+    [ TextFieldApi "getTextField" parseLenEncField+    , TextFieldApi "RowDecoder" rowDecoderField+    , TextFieldApi "RawRow" (rawRowField (const Decode.mysqlValue))+    , TextFieldApi "typed decoders" (rawRowField typedDecoder)+    ]++-- | A one-column row decoded by a 'Decode.RowDecoder' with 'Decode.mysqlValue'.+rowDecoderField :: ColumnDef -> B.ByteString -> Either String MySQLValue+rowDecoderField cd payload = do+    prepared <- first show (Decode.prepareRowDecoder @Decode.TextProtocol+                                (Decode.field Decode.mysqlValue) (V.singleton cd))+    first show (Decode.runPreparedRow prepared (lenEncoded payload))++-- | A one-column 'Decode.RawRow' read by column number with the decoder for+-- the column's kind.+rawRowField :: (ColumnKind -> Decode.FieldDecoder MySQLValue) -> ColumnDef -> B.ByteString+            -> Either String MySQLValue+rawRowField decoderFor cd payload = do+    raw <- first show (Decode.textRawRow 1 (lenEncoded payload))+    parser <- first show (Decode.prepareFieldParser (decoderFor (columnKind cd)) (ColumnNumber 0) cd)+    Decode.runFieldParser parser raw (ColumnNumber 0) (Left . show) Right++-- | Check that parsing yields a Left (failure), not a crash or garbage.+assertParseFailure :: String -> Either String a -> Assertion+assertParseFailure label result = case result of+    Left _  -> pure ()+    Right _ -> assertFailure (label ++ ": expected parse failure but got a result")++--------------------------------------------------------------------------------+-- Text protocol tests++textProtocolTests :: TextFieldApi -> TestTree+textProtocolTests (TextFieldApi name parseField) = testGroup name+    [ testCase "timestamp: date-only no time part" $ do+        -- "2024-01-01" is 10 bytes. dateParser succeeds, then+        -- unsafeDrop 11 on a 10-byte string is UB.+        -- With "abc" this wouldn't trigger because readDecimal "abc" → Nothing+        -- short-circuits via Maybe's Applicative before unsafeDrop is forced.+        let cd = mkColumnDef mySQLTypeTimestamp+            result = parseField cd "2024-01-01"+        assertParseFailure "timestamp no time" result++    , testCase "timestamp: truncated time" $ do+        -- "2024-01-01 1" is 12 bytes. dateParser succeeds, unsafeDrop 11+        -- gives "1", timeParser reads hour=1, rest="", then unsafeTail+        -- on empty remainder is UB.+        let cd = mkColumnDef mySQLTypeTimestamp+            result = parseField cd "2024-01-01 1"+        assertParseFailure "timestamp truncated time" result++    , testCase "datetime: date-only no time part" $ do+        -- Same as timestamp: dateParser succeeds on "2024-01-01",+        -- then unsafeDrop 11 on 10-byte string is UB.+        let cd = mkColumnDef mySQLTypeDateTime+            result = parseField cd "2024-01-01"+        assertParseFailure "datetime no time" result++    , testCase "time: empty input" $ do+        -- TIME parser does unsafeIndex 0 on the payload; empty = UB+        let cd = mkColumnDef mySQLTypeTime+            result = parseField cd ""+        assertParseFailure "time empty" result++    , testCase "date: missing separator" $ do+        -- "2024" has no '-' separator; readDecimal consumes everything,+        -- then unsafeTail is called on an empty remainder+        let cd = mkColumnDef mySQLTypeDate+            result = parseField cd "2024"+        assertParseFailure "date missing sep" result++    , testCase "time: missing separator" $ do+        -- "12" has no ':' separator; readDecimal consumes it,+        -- then unsafeTail is called on empty remainder+        let cd = mkColumnDef mySQLTypeTime+            result = parseField cd "12"+        assertParseFailure "time missing sep" result+    ]++--------------------------------------------------------------------------------+-- BinLog value tests++binlogValueTests :: [TestTree]+binlogValueTests =+    [ testCase "NEWDECIMAL: precision < scale causes underflow" $ do+        -- precision=3, scale=10: (precision - scale) underflows Word8 to 249,+        -- producing a huge index into the 10-element sizeTable+        let meta = BINLOG_TYPE_NEWDECIMAL 3 10+            -- Provide some bytes so Get doesn't fail on "not enough input" first+            input = B.replicate 64 0x80+            result = parseOnly (getBinLogField meta) input+        assertParseFailure "NEWDECIMAL underflow" result+    ]++--------------------------------------------------------------------------------+-- BinLog event tests++binlogEventTests :: [TestTree]+binlogEventTests =+    [ testCase "eventHeaderLen: BINLOG_UNKNOWN_EVENT (negative index)" $ do+        -- fromEnum BINLOG_UNKNOWN_EVENT - 1 = -1, negative index into ByteString+        let fd = FormatDescription+                { fdVersion = 4+                , fdMySQLVersion = B.replicate 50 0+                , fdCreateTime = 0+                , fdEventHeaderLenVector = B.pack [19, 19, 19]  -- short vector+                }+            -- This should not crash; should return safe fallback+            result = eventHeaderLen fd BINLOG_UNKNOWN_EVENT+        -- After fix: returns 0 (safe fallback) instead of crashing+        assertEqual "unknown event should return 0" 0 result+    ]
+ test/CABundle.hs view
@@ -0,0 +1,14 @@+-- | The Mozilla CA bundle behind 'MozillaCAStore', which an installed library+-- lacked: it was packaged as an extra source file, not a data file.+module CABundle (tests) where++import           Data.TLSSetting   (mozillaCAStorePath)+import           System.Directory  (doesFileExist)+import           Test.Tasty+import           Test.Tasty.HUnit++tests :: TestTree+tests = testCase "the Mozilla CA bundle is installed" $ do+    path <- mozillaCAStorePath+    exists <- doesFileExist path+    assertBool ("no CA bundle at " ++ path) exists
+ test/CachingSha2.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE ScopedTypeVariables #-}++module CachingSha2 (tests) where++import Database.MySQL.Base+import QueryApi+import qualified System.IO.Streams as Stream+import Test.Tasty+import Test.Tasty.HUnit++-- | These tests exercise two different authentication paths in 'completeAuth'.+-- Both tests connect and run @SELECT 1@, but the server-side auth protocol+-- differs based on which MySQL user is used. The users are created in+-- @nix/ci.nix@ (integrated-checks-mysql80) with different auth plugins:+--+-- * @testMySQLHaskellSha2@ — created with @caching_sha2_password@ (the MySQL 8.0+--   default). The client sends a SHA256 scramble, the server responds with+--   AuthMoreData (0x01, byte 2 = 0x03) indicating fast auth success.+--   The CI script pre-caches the verifier via a unix socket login so the+--   fast path is guaranteed.+--+-- * @testMySQLHaskellNative@ — created with @mysql_native_password@. The server+--   advertises @caching_sha2_password@ in its Greeting, so the client initially+--   sends a SHA256 scramble. The server then responds with AuthSwitchRequest+--   (0xFE) telling the client to re-authenticate using @mysql_native_password@+--   with a new salt. The client re-scrambles with SHA1 and sends the response.+tests :: QueryApi -> TestTree+tests api = testGroup "caching_sha2_password"+    [ testCaseSteps "SHA256 fast auth" $ \step -> do+        step "connecting as testMySQLHaskellSha2 (caching_sha2_password)..."+        (_, c) <- connectDetail defaultConnectInfo+            { ciUser = "testMySQLHaskellSha2"+            , ciPassword = "testPassword123"+            , ciDatabase = "testMySQLHaskell"+            }++        step "executing SELECT 1..."+        (_, is) <- apiQuery_ api (ColumnCount 1) c "SELECT 1"+        Just row <- Stream.read is+        assertBool "SELECT 1 returns 1" (row == [MySQLInt32 1] || row == [MySQLInt64 1])+        Stream.skipToEof is++        close c++    , testCaseSteps "AuthSwitchRequest handling (mysql_native_password on sha2 server)" $ \step -> do+        step "connecting as testMySQLHaskellNative (mysql_native_password)..."+        (_, c) <- connectDetail defaultConnectInfo+            { ciUser = "testMySQLHaskellNative"+            , ciPassword = "nativePass123"+            , ciDatabase = "testMySQLHaskell"+            }++        step "executing SELECT 1..."+        (_, is) <- apiQuery_ api (ColumnCount 1) c "SELECT 1"+        Just row <- Stream.read is+        assertBool "SELECT 1 returns 1" (row == [MySQLInt32 1] || row == [MySQLInt64 1])+        Stream.skipToEof is++        close c+    ]
+ test/DecoderRoundtrip.hs view
@@ -0,0 +1,179 @@+{-# LANGUAGE TypeApplications #-}++-- | 'FieldRoundtrip' for the typed decoders of "Database.MySQL.Decoder": the+-- same types, the same decode rules. The roundtrip goes through the bytes a+-- prepared statement's row carries: a value 'toField' gives, encoded by the+-- old 'putBinaryField', must decode back to itself.+module DecoderRoundtrip (tests) where++import           Data.Binary.Put                    (runPut)+import           Data.ByteString                    (ByteString)+import qualified Data.ByteString                    as B+import qualified Data.ByteString.Lazy               as LazyByteString+import           Data.Fixed                         (Fixed (MkFixed), Pico)+import           Data.Scientific                    (Scientific, scientific)+import qualified Data.Text                          as Text+import qualified Data.Text.Lazy                     as LazyText+import           Data.Time.Calendar                 (Day, fromGregorian)+import           Data.Time.LocalTime                (LocalTime (..),+                                                     TimeOfDay (..))+import qualified Data.Vector                        as V+import           Data.Word                          (Word16)+import qualified Database.MySQL.Decoder             as Decode+import           Database.MySQL.Field+import           Database.MySQL.Protocol.ColumnDef+import           Database.MySQL.Protocol.MySQLValue (ColumnNumber (..),+                                                     MySQLValue (..),+                                                     putBinaryField)+import           Test.QuickCheck                    (Gen, Property, arbitrary,+                                                     choose, forAll, (===))+import           Test.QuickCheck.Instances          ()+import           Test.Tasty+import           Test.Tasty.HUnit                   (testCase, (@?=))+import           Test.Tasty.QuickCheck              (testProperty)++tests :: TestTree+tests = testGroup "Decoder"+    [ testGroup "roundtrip through a prepared statement's row"+        [ testProperty "Int8" (roundtrips Decode.int8 (column mySQLTypeTiny signed binary))+        , testProperty "Word8" (roundtrips Decode.word8 (column mySQLTypeTiny unsigned binary))+        , testProperty "Int16" (roundtrips Decode.int16 (column mySQLTypeShort signed binary))+        , testProperty "Word16" (roundtrips Decode.word16 (column mySQLTypeShort unsigned binary))+        , testProperty "Int32" (roundtrips Decode.int32 (column mySQLTypeLong signed binary))+        , testProperty "Word32" (roundtrips Decode.word32 (column mySQLTypeLong unsigned binary))+        , testProperty "Int64" (roundtrips Decode.int64 (column mySQLTypeLongLong signed binary))+        , testProperty "Word64" (roundtrips Decode.word64 (column mySQLTypeLongLong unsigned binary))+        , testProperty "Float" (roundtrips Decode.float (column mySQLTypeFloat signed binary))+        , testProperty "Double" (roundtrips Decode.double (column mySQLTypeDouble signed binary))+        , testProperty "Scientific" $ forAll genDecimal $+            roundtrips Decode.scientific (column mySQLTypeNewDecimal signed binary)+        , testProperty "Text" (roundtrips Decode.text textColumn)+        , testProperty "lazy Text" (roundtrips (LazyText.fromStrict <$> Decode.text) textColumn)+        , testProperty "String" (roundtrips (Text.unpack <$> Decode.text) textColumn)+        , testProperty "ByteString" (roundtrips Decode.bytes bytesColumn)+        , testProperty "lazy ByteString"+            (roundtrips (LazyByteString.fromStrict <$> Decode.bytes) bytesColumn)+        , testProperty "Day" $ forAll genDay $+            roundtrips Decode.day (column mySQLTypeDate signed binary)+        , testProperty "LocalTime" $ forAll genLocalTime $+            roundtrips Decode.localTime (column mySQLTypeDateTime signed binary)+        , testProperty "TimeOfDay" $ forAll genTimeOfDay $+            roundtrips Decode.timeOfDay (column mySQLTypeTime signed binary)+        , testProperty "Bool" (roundtrips Decode.bool (column mySQLTypeTiny signed binary))+        , testProperty "Maybe Int32"+            (roundtrips (Decode.nullable Decode.int32) (column mySQLTypeLong signed binary))+        , testProperty "Maybe Text" (roundtrips (Decode.nullable Decode.text) textColumn)+        ]+    , testGroup "decode failures"+        [ testCase "NULL into a non-Maybe decoder is FieldUnexpectedNull" $+            decodeBinary Decode.text textColumn MySQLNull+                @?= Left (show (Decode.FieldError (ColumnNumber 0) Decode.FieldUnexpectedNull))+        , testCase "a column of another type is ColumnTypeMismatch" $+            decodeBinary Decode.int8 textColumn (MySQLText "hi")+                @?= Left (show (Decode.ColumnTypeMismatch (ColumnNumber 0) "" mySQLTypeVarString "Int8"))+        , testCase "no implicit widening: an INT column into int64 fails" $+            decodeBinary Decode.int64 (column mySQLTypeLong signed binary) (MySQLInt32 42)+                @?= Left (show (Decode.ColumnTypeMismatch (ColumnNumber 0) "" mySQLTypeLong "Int64"))+        , testCase "negative TIME into timeOfDay fails" $+            decodeBinary Decode.timeOfDay (column mySQLTypeTime signed binary)+                (MySQLTime 1 (TimeOfDay 1 2 3))+                @?= Left (show (Decode.FieldError (ColumnNumber 0)+                                    (Decode.FieldNegativeTime+                                        (B.drop 1 (encodedField (MySQLTime 1 (TimeOfDay 1 2 3)))))))+        ]+    , testGroup "decode alternatives"+        [ testCase "NULL into nullable is Nothing" $+            decodeBinary (Decode.nullable Decode.int32) (column mySQLTypeLong signed binary) MySQLNull+                @?= Right Nothing+        , testCase "TIMESTAMP decodes into LocalTime" $+            decodeBinary Decode.localTime (column mySQLTypeTimestamp signed binary)+                (MySQLTimeStamp (read "2026-07-21 12:00:00"))+                @?= Right (read "2026-07-21 12:00:00")+        , testCase "nonzero TINYINT decodes as True" $+            decodeBinary Decode.bool (column mySQLTypeTiny signed binary) (MySQLInt8 5)+                @?= Right True+        , testCase "unsigned TINYINT decodes into Bool" $+            decodeBinary Decode.bool (column mySQLTypeTiny unsigned binary) (MySQLInt8U 0)+                @?= Right False+        , testCase "nested nullable collapses a NULL to the outer Nothing (as Field does)" $+            decodeBinary (Decode.nullable (Decode.nullable Decode.int32))+                (column mySQLTypeLong signed binary) MySQLNull+                @?= Right Nothing+        , testCase "NaN decodes losslessly" $+            fmap isNaN (decodeBinary Decode.double (column mySQLTypeDouble signed binary)+                            (MySQLDouble (0 / 0)))+                @?= Right True+        ]+    ]++-- | 'toField', encoded as a prepared statement's row and decoded, gives the+-- value back.+roundtrips :: (Field a, Eq a, Show a) => Decode.FieldDecoder a -> ColumnDef -> a -> Property+roundtrips decoder definition value =+    decodeBinary decoder definition (toField value) === Right value++-- | A one-column binary-protocol row holding the value as 'putBinaryField'+-- encodes it, decoded with the decoder; errors are shown, so that column+-- mismatches and field errors compare in one type.+decodeBinary :: Decode.FieldDecoder a -> ColumnDef -> MySQLValue -> Either String a+decodeBinary decoder definition value =+    case Decode.prepareRowDecoder @Decode.BinaryProtocol (Decode.field decoder) (V.singleton definition) of+        Left mismatch  -> Left (show mismatch)+        Right prepared -> either (Left . show) Right (Decode.runPreparedRow prepared (binaryRow value))++-- | The 0x00 header and the NULL map (column 0 is bit 2), then the field.+binaryRow :: MySQLValue -> ByteString+binaryRow value = case value of+    MySQLNull -> B.pack [0x00, 0x04]+    _         -> B.concat [B.pack [0x00, 0x00], encodedField value]++-- | The value as 'putBinaryField' writes it; for the length-prefixed types+-- that includes the length byte, which a decoder's field bytes do not.+encodedField :: MySQLValue -> ByteString+encodedField value = LazyByteString.toStrict (runPut (putBinaryField value))++column :: FieldType -> Word16 -> Word16 -> ColumnDef+column fieldType flags charSet = ColumnDef+    { columnDB        = ""+    , columnTable     = ""+    , columnOrigTable = ""+    , columnName      = ""+    , columnOrigName  = ""+    , columnCharSet   = charSet+    , columnLength    = 0+    , columnType      = fieldType+    , columnFlags     = flags+    , columnDecimals  = 0+    }++signed, unsigned :: Word16+signed = 0+unsigned = 32++binary, utf8 :: Word16+binary = 63+utf8 = 33++textColumn, bytesColumn :: ColumnDef+textColumn = column mySQLTypeVarString signed utf8+bytesColumn = column mySQLTypeBlob signed binary++-- | DECIMALs as MySQL holds them: a coefficient and a modest exponent, which+-- 'putBinaryField' writes out in full.+genDecimal :: Gen Scientific+genDecimal = scientific <$> arbitrary <*> choose (-30, 30)++-- | Days the 2-byte year of the binary protocol carries.+genDay :: Gen Day+genDay = fromGregorian <$> choose (0, 9999) <*> choose (1, 12) <*> choose (1, 28)++-- | DATETIMEs to the microsecond, the binary protocol's resolution.+genLocalTime :: Gen LocalTime+genLocalTime = LocalTime <$> genDay <*> (TimeOfDay <$> choose (0, 23) <*> choose (0, 59) <*> genSeconds)++-- | TIMEs up to MySQL's 838 hours, to the microsecond.+genTimeOfDay :: Gen TimeOfDay+genTimeOfDay = TimeOfDay <$> choose (0, 838) <*> choose (0, 59) <*> genSeconds++genSeconds :: Gen Pico+genSeconds = MkFixed <$> ((* 1000000) <$> choose (0, 59999999))
+ test/DirectDecoding.hs view
@@ -0,0 +1,328 @@+-- | The decoders of "Database.MySQL.Decoder" against the paths they bypass:+-- 'mysqlValue' must give the values 'decodeTextRow' and 'getBinaryRow' give,+-- and each typed decoder the value "Database.MySQL.Field" gets from the same+-- field through 'MySQLValue', in both protocols.+module DirectDecoding (tests) where++import           Data.Binary.Parser                 (parseOnly)+import           Data.Binary.Put                    (putByteString, putDoublele,+                                                     putFloatle, runPut)+import           Data.Bits                          (setBit)+import           Data.ByteString                    (ByteString)+import qualified Data.ByteString                    as B+import qualified Data.ByteString.Char8              as BC+import qualified Data.ByteString.Lazy               as L+import           Data.Int                           (Int32)+import           Data.Maybe                         (fromMaybe, isJust, isNothing)+import           Data.Text                          (Text)+import qualified Data.Text.Encoding                 as T+import           Data.Time.Calendar                 (Day, fromGregorian)+import qualified Data.Vector                        as V+import           Data.Word                          (Word8)+import qualified Database.MySQL.Decoder             as Decode+import           Database.MySQL.Field+import           Database.MySQL.Protocol.ColumnDef+import           Database.MySQL.Protocol.MySQLValue+import           Database.MySQL.Protocol.Packet     (putLenEncInt)+import           Test.QuickCheck+import           Test.QuickCheck.Instances          ()+import           Test.Tasty+import           Test.Tasty.HUnit                   (testCase, (@?=))+import           Test.Tasty.QuickCheck              (testProperty)+import           TextRowDecoding                    (binary, columnOf,+                                                     employeeColumns,+                                                     employeeRow, encodeRow,+                                                     genColumn, genField, genRow,+                                                     utf8)++tests :: TestTree+tests = testGroup "direct decoding"+    [ testProperty "text rows: mysqlValue gives what decodeTextRow gives" $+        forAll genRow $ \(columns, fields) ->+            forAll (choose (0, B.length (encodeRow fields))) $ \kept ->+                conjoin+                    [ textMySQLValues columns (encodeRow fields)+                        === shownRow (decodeTextRow (map columnKind columns) (encodeRow fields))+                    , textMySQLValues columns (B.take kept (encodeRow fields))+                        === shownRow (decodeTextRow (map columnKind columns) (B.take kept (encodeRow fields)))+                    ]+    , testProperty "binary rows: mysqlValue gives what getBinaryRow gives" $ checkCoverage $+        forAll genBinaryRow $ \(columns, row) ->+            forAll (choose (0, B.length row)) $ \kept ->+                cover 25 (isJust (getBinaryRowValues columns row)) "the whole row decodes" $+                conjoin+                    [ binaryMySQLValues columns row === getBinaryRowValues columns row+                    , binaryMySQLValues columns (B.take kept row)+                        === getBinaryRowValues columns (B.take kept row)+                    ]+    , testProperty "binary rows: decodeBinaryValues gives what getBinaryRow gives" $ checkCoverage $+        forAll genBinaryRow $ \(columns, row) ->+            forAll (choose (0, B.length row)) $ \kept ->+                cover 25 (isJust (getBinaryRowValues columns row)) "the whole row decodes" $+                conjoin+                    [ shownRow (Decode.decodeBinaryValues (Decode.binaryValueColumns columns) row)+                        === getBinaryRowValues columns row+                    , shownRow (Decode.decodeBinaryValues (Decode.binaryValueColumns columns) (B.take kept row))+                        === getBinaryRowValues columns (B.take kept row)+                    ]+    , testProperty "text rows: by column number gives what the row decoder gives" $ checkCoverage $+        forAll genRow $ \(columns, fields) ->+            forAll (choose (0, B.length (encodeRow fields))) $ \kept ->+                cover 25 (isJust (textMySQLValues columns (encodeRow fields))) "the whole row decodes" $+                conjoin+                    [ rawRowValues columns (Decode.textRawRow (length columns) (encodeRow fields))+                        === textMySQLValues columns (encodeRow fields)+                    , rawRowValues columns (Decode.textRawRow (length columns) (B.take kept (encodeRow fields)))+                        === textMySQLValues columns (B.take kept (encodeRow fields))+                    ]+    , testProperty "binary rows: by column number gives what the row decoder gives" $ checkCoverage $+        forAll genBinaryRow $ \(columns, row) ->+            forAll (choose (0, B.length row)) $ \kept ->+                cover 25 (isJust (binaryMySQLValues columns row)) "the whole row decodes" $+                conjoin+                    [ rawRowValues columns (binaryRawRowOf columns row) === binaryMySQLValues columns row+                    , rawRowValues columns (binaryRawRowOf columns (B.take kept row))+                        === binaryMySQLValues columns (B.take kept row)+                    ]+    , testProperty "text fields: a typed decoder gives what Field gives" $ checkCoverage $+        forAll genColumn $ \column -> forAll (genField (columnKind column)) $ \fieldBytes ->+            case comparisonFor column of+                Nothing -> property True+                Just (Comparison decoder viaField) ->+                    cover 40 (isJust (viaFieldText viaField column (encodeRow [fieldBytes])))+                        "the field decodes" $+                    typedText decoder column (encodeRow [fieldBytes])+                        === viaFieldText viaField column (encodeRow [fieldBytes])+    , testProperty "binary fields: a typed decoder gives what Field gives" $ checkCoverage $+        forAll genColumn $ \column -> forAll (genBinaryField column) $ \fieldBytes ->+            case comparisonFor column of+                Nothing -> property True+                Just (Comparison decoder viaField) ->+                    cover 40 (isJust (viaFieldBinary viaField column (encodeBinaryRow [fieldBytes])))+                        "the field decodes" $+                    typedBinary decoder column (encodeBinaryRow [fieldBytes])+                        === viaFieldBinary viaField column (encodeBinaryRow [fieldBytes])+    , testCase "a raw row knows where each field is" $+        fmap (\row -> map (Decode.rawField row . ColumnNumber) [0, 1, 6]) (Decode.textRawRow 6 employeeRow)+            @?= Right [Decode.RawBytes "10001", Decode.RawBytes "1953-09-02", Decode.RawAbsent]+    , testCase "a column number past the row is absent, however large" $+        fmap (\row -> map (Decode.rawField row . ColumnNumber) [6, 2 ^ (62 :: Int), 2 ^ (62 :: Int) + 1000, -1])+             (Decode.textRawRow 6 employeeRow)+            @?= Right (replicate 4 Decode.RawAbsent)+    , testCase "a record decoder reads the employees row" $+        decodeTextWith employeeDecoder employeeColumns employeeRow+            @?= Decoded (10001, fromGregorian 1953 9 2, "Georgi", "Facello", "M", fromGregorian 1986 6 26)+    , testCase "a decoder for fewer columns than the result set has" $+        decodeTextWith (Decode.field Decode.int32) employeeColumns employeeRow+            @?= Mismatch (Decode.ColumnCountMismatch 1 6)+    , testCase "a decoder that does not take the column's type" $+        decodeTextWith (Decode.field Decode.text) [columnOf mySQLTypeLong 0 binary] (encodeRow [Just "1"])+            @?= Mismatch (Decode.ColumnTypeMismatch (ColumnNumber 0) "" mySQLTypeLong "Text")+    , testCase "NULL is Nothing for a nullable decoder" $+        decodeTextWith (Decode.field (Decode.nullable Decode.int32)) [columnOf mySQLTypeLong 0 binary]+            (encodeRow [Nothing])+            @?= Decoded Nothing+    , testCase "NULL is an error for any other decoder" $+        decodeTextWith (Decode.field Decode.int32) [columnOf mySQLTypeLong 0 binary] (encodeRow [Nothing])+            @?= FieldFailed (Decode.FieldError (ColumnNumber 0) Decode.FieldUnexpectedNull)+    , testCase "a negative TIME is not a TimeOfDay" $+        decodeTextWith (Decode.field Decode.timeOfDay) [columnOf mySQLTypeTime 0 binary]+            (encodeRow [Just "-01:00:00"])+            @?= FieldFailed (Decode.FieldError (ColumnNumber 0) (Decode.FieldNegativeTime "-01:00:00"))+    , testCase "invalid UTF-8 is an error, not an exception" $+        decodeTextWith (Decode.field Decode.text) [columnOf mySQLTypeVarString 0 utf8]+            (encodeRow [Just (B.pack [0xff])])+            @?= FieldFailed (Decode.FieldError (ColumnNumber 0) (Decode.FieldInvalidUtf8 (B.pack [0xff])))+    , testCase "mysqlValue checks UTF-8 while it reads the row, unlike query_" $+        decodeTextWith (Decode.field Decode.mysqlValue) [columnOf mySQLTypeVarString 0 utf8]+            (encodeRow [Just (B.pack [0xff])])+            @?= FieldFailed (Decode.FieldError (ColumnNumber 0) (Decode.FieldInvalidUtf8 (B.pack [0xff])))+    , testCase "the binary protocol's signed integers are two's complement" $+        decodeBinaryWith (Decode.field Decode.int32) [columnOf mySQLTypeLong 0 binary]+            (encodeBinaryRow [Just (B.pack [0xfe, 0xff, 0xff, 0xff])])+            @?= Decoded (-2)+    ]++employeeDecoder :: Decode.RowDecoder (Int32, Day, Text, Text, Text, Day)+employeeDecoder = (,,,,,)+    <$> Decode.field Decode.int32+    <*> Decode.field Decode.day+    <*> Decode.field Decode.text+    <*> Decode.field Decode.text+    <*> Decode.field Decode.text+    <*> Decode.field Decode.day++-- | What decoding one row came to.+data Outcome a+    = Mismatch Decode.ColumnMismatch+    | FieldFailed Decode.FieldError+    | Decoded a+    deriving (Show, Eq)++decodeTextWith :: Decode.RowDecoder a -> [ColumnDef] -> ByteString -> Outcome a+decodeTextWith decoder columns row = case Decode.prepareRowDecoder @Decode.TextProtocol decoder (V.fromList columns) of+    Left mismatch  -> Mismatch mismatch+    Right prepared -> either FieldFailed Decoded (Decode.runPreparedRow prepared row)++decodeBinaryWith :: Decode.RowDecoder a -> [ColumnDef] -> ByteString -> Outcome a+decodeBinaryWith decoder columns row = case Decode.prepareRowDecoder @Decode.BinaryProtocol decoder (V.fromList columns) of+    Left mismatch  -> Mismatch mismatch+    Right prepared -> either FieldFailed Decoded (Decode.runPreparedRow prepared row)++-- | Every column through 'Decode.mysqlValue'.+allValues :: [ColumnDef] -> Decode.RowDecoder [MySQLValue]+allValues = traverse (const (Decode.field Decode.mysqlValue))++-- | Every column through 'Decode.mysqlValue' on a 'Decode.RawRow', by column+-- number, as a library decoding by column number would.+rawRowValues :: Decode.RowProtocol protocol+             => [ColumnDef] -> Either RowError (Decode.RawRow protocol) -> Maybe String+rawRowValues columns rawRow = do+    raw <- either (const Nothing) Just rawRow+    parsers <- either (const Nothing) Just+        (traverse (uncurry (Decode.prepareFieldParser Decode.mysqlValue)) (zip (map ColumnNumber [0 ..]) columns))+    shownRow (traverse (rawFieldValue raw) (zip (map ColumnNumber [0 ..]) parsers))++rawFieldValue :: Decode.RawRow protocol -> (ColumnNumber, Decode.FieldParser protocol MySQLValue)+              -> Either Decode.FieldError MySQLValue+rawFieldValue raw (column, parser) = Decode.runFieldParser parser raw column Left Right++-- | Values are compared shown, so that a NaN read from random bytes equals itself.+shownRow :: Either rowError [MySQLValue] -> Maybe String+shownRow = either (const Nothing) (Just . show)++binaryRawRowOf :: [ColumnDef] -> ByteString -> Either RowError (Decode.RawRow Decode.BinaryProtocol)+binaryRawRowOf columns = Decode.binaryRawRow (V.fromList (map Decode.binaryWidth columns))++textMySQLValues :: [ColumnDef] -> ByteString -> Maybe String+textMySQLValues columns row = shownOutcome (decodeTextWith (allValues columns) columns row)++binaryMySQLValues :: [ColumnDef] -> ByteString -> Maybe String+binaryMySQLValues columns row = shownOutcome (decodeBinaryWith (allValues columns) columns row)++getBinaryRowValues :: [ColumnDef] -> ByteString -> Maybe String+getBinaryRowValues columns row = shownRow (parseOnly (getBinaryRow columns (length columns)) row)++-- | A typed decoder and the "Database.MySQL.Field" conversion for the same type.+data Comparison = forall a. Show a => Comparison (Decode.FieldDecoder a) (MySQLValue -> Either DecodeError a)++-- | The comparison for a column's kind; YEAR, GEOMETRY and BIT have no typed+-- decoder, and a column of type NULL has no kind.+comparisonFor :: ColumnDef -> Maybe Comparison+comparisonFor column = case columnKind column of+    NullColumn -> Nothing+    ValueColumn _ kind -> case kind of+        KindDecimal     -> Just (Comparison Decode.scientific decodeScientific)+        KindInt8U       -> Just (Comparison Decode.word8 decodeWord8)+        KindInt8        -> Just (Comparison Decode.int8 decodeInt8)+        KindInt16U      -> Just (Comparison Decode.word16 decodeWord16)+        KindInt16       -> Just (Comparison Decode.int16 decodeInt16)+        KindInt32U      -> Just (Comparison Decode.word32 decodeWord32)+        KindInt32       -> Just (Comparison Decode.int32 decodeInt32)+        KindInt64U      -> Just (Comparison Decode.word64 decodeWord64)+        KindInt64       -> Just (Comparison Decode.int64 decodeInt64)+        KindFloat       -> Just (Comparison Decode.float decodeFloat')+        KindDouble      -> Just (Comparison Decode.double decodeDouble)+        KindYear        -> Nothing+        KindTimeStamp   -> Just (Comparison Decode.localTime decodeLocalTime)+        KindDateTime    -> Just (Comparison Decode.localTime decodeLocalTime)+        KindDate        -> Just (Comparison Decode.day decodeDay)+        KindTime        -> Just (Comparison Decode.timeOfDay decodeTimeOfDay)+        KindGeometry    -> Nothing+        KindText _      -> Just (Comparison Decode.text decodeText)+        KindLatin1Text  -> Just (Comparison Decode.text decodeText)+        KindBytes       -> Just (Comparison Decode.bytes decodeByteString)+        KindBit         -> Nothing+        KindUnsupported -> Nothing++typedText :: Show a => Decode.FieldDecoder a -> ColumnDef -> ByteString -> Maybe String+typedText decoder column row = shownOutcome (decodeTextWith (Decode.field decoder) [column] row)++viaFieldText :: Show a => (MySQLValue -> Either DecodeError a) -> ColumnDef -> ByteString -> Maybe String+viaFieldText viaField column row = case decodeTextRow [columnKind column] row of+    Right [value] -> either (const Nothing) (Just . show) (viaField value)+    Right values  -> Just ("unexpected values: " ++ show values)+    Left _        -> Nothing++typedBinary :: Show a => Decode.FieldDecoder a -> ColumnDef -> ByteString -> Maybe String+typedBinary decoder column row = shownOutcome (decodeBinaryWith (Decode.field decoder) [column] row)++-- | The value shown, 'Nothing' for a row or field that did not decode; a column+-- mismatch is a failure of the test, so it shows up in the comparison.+shownOutcome :: Show a => Outcome a -> Maybe String+shownOutcome decoded = case decoded of+    Decoded value     -> Just (show value)+    FieldFailed _     -> Nothing+    Mismatch mismatch -> Just ("mismatch: " ++ show mismatch)++viaFieldBinary :: Show a => (MySQLValue -> Either DecodeError a) -> ColumnDef -> ByteString -> Maybe String+viaFieldBinary viaField column row = case parseOnly (getBinaryRow [column] 1) row of+    Right [value] -> either (const Nothing) (Just . show) (viaField value)+    Right values  -> Just ("unexpected values: " ++ show values)+    Left _        -> Nothing++genBinaryRow :: Gen ([ColumnDef], ByteString)+genBinaryRow = do+    columnCount <- choose (0, 8)+    columns <- vectorOf columnCount genColumn+    fields <- mapM genBinaryField columns+    pure (columns, encodeBinaryRow fields)++-- | A field as a prepared statement's row carries it: 'Nothing' for NULL, which+-- a column of type NULL always is, otherwise the bytes after the NULL map.+genBinaryField :: ColumnDef -> Gen (Maybe ByteString)+genBinaryField column = case columnKind column of+    NullColumn         -> pure Nothing+    ValueColumn _ kind -> frequency [(1, pure Nothing), (6, Just <$> genBinaryValue kind)]++-- | Mostly what the server sends, sometimes lengths or bytes it would not, such+-- as a string that is not UTF-8 in a character set read as UTF-8.+genBinaryValue :: ValueKind -> Gen ByteString+genBinaryValue kind = case kind of+    KindDecimal     -> lengthEncoded . BC.pack . show <$> (arbitrary :: Gen Double)+    KindInt8U       -> genBytes 1+    KindInt8        -> genBytes 1+    KindInt16U      -> genBytes 2+    KindInt16       -> genBytes 2+    KindInt32U      -> genBytes 4+    KindInt32       -> genBytes 4+    KindInt64U      -> genBytes 8+    KindInt64       -> genBytes 8+    KindFloat       -> L.toStrict . runPut . putFloatle <$> arbitrary+    KindDouble      -> L.toStrict . runPut . putDoublele <$> arbitrary+    KindYear        -> genBytes 2+    KindTimeStamp   -> lengthEncoded <$> (elements [0, 4, 7, 11, 5] >>= genBytes)+    KindDateTime    -> lengthEncoded <$> (elements [0, 4, 7, 11, 5] >>= genBytes)+    KindDate        -> lengthEncoded <$> (elements [0, 4, 3] >>= genBytes)+    KindTime        -> lengthEncoded <$> (elements [0, 8, 12, 9] >>= genBytes)+    KindGeometry    -> lengthEncoded <$> arbitrary+    -- Valid UTF-8 only, as in TextRowDecoding's genField.+    KindText _      -> lengthEncoded . T.encodeUtf8 <$> arbitrary+    KindLatin1Text  -> lengthEncoded <$> arbitrary+    KindBytes       -> lengthEncoded <$> arbitrary+    KindBit         -> lengthEncoded <$> (choose (1, 10) >>= genBytes)+    KindUnsupported -> lengthEncoded <$> arbitrary++genBytes :: Int -> Gen ByteString+genBytes count = B.pack <$> vector count++lengthEncoded :: ByteString -> ByteString+lengthEncoded fieldBytes =+    L.toStrict (runPut (putLenEncInt (B.length fieldBytes) >> putByteString fieldBytes))++-- | The 0x00 header, the NULL map with column @i@ at bit @i + 2@, then the+-- non-NULL fields.+encodeBinaryRow :: [Maybe ByteString] -> ByteString+encodeBinaryRow fields = B.concat+    (B.singleton 0x00 : binaryNullMap fields : map (fromMaybe B.empty) fields)++binaryNullMap :: [Maybe ByteString] -> ByteString+binaryNullMap fields = B.pack+    (map (nullMapByte (nullBits fields)) [0 .. (length fields + 7 + 2) `div` 8 - 1])++nullBits :: [Maybe ByteString] -> [Int]+nullBits fields = map fst (filter (isNothing . snd) (zip [2 ..] fields))++nullMapByte :: [Int] -> Int -> Word8+nullMapByte bits byteIndex =+    foldr (flip setBit) 0 (map (subtract (8 * byteIndex)) (filter ((== byteIndex) . (`div` 8)) bits))
+ test/Ed25519Scramble.hs view
@@ -0,0 +1,96 @@+-- | MariaDB's ed25519 authentication, checked against MariaDB's own answers:+-- the keys its server stores and the signatures its client library sends.+module Ed25519Scramble (tests) where++import           Control.Exception              (try)+import qualified Crypto.PubKey.Ed25519          as Ed25519+import           Crypto.Error                   (eitherCryptoError)+import           Data.Binary.Put                (putByteString, putWord8)+import qualified Data.ByteArray.Encoding        as Encoding+import qualified Data.ByteString                as B+import qualified Data.ByteString.Base16         as Base16+import qualified Data.ByteString.Char8          as BC+import qualified Data.ByteString.Lazy           as L+import           Data.IORef                     (modifyIORef', newIORef, readIORef)+import           Database.MySQL.Connection      (UnexpectedPacket (..), completeAuth,+                                                 ed25519PublicKey, scrambleEd25519)+import           Database.MySQL.Protocol.Packet (Packet (..), putToPacket)+import qualified System.IO.Streams              as Stream+import           Test.QuickCheck.Instances      ()+import           Test.Tasty+import           Test.Tasty.HUnit+import           Test.Tasty.QuickCheck++hex :: String -> IO B.ByteString+hex = either (assertFailure . ("not hex: " ++)) pure . Base16.decode . BC.pack++-- | The key as MariaDB shows it: base64 without its one padding character.+mariadbKey :: B.ByteString -> B.ByteString+mariadbKey = B.dropWhileEnd (== 0x3D) . Encoding.convertToBase Encoding.Base64++-- | What the server sends when an account needs client_ed25519.+authSwitch :: B.ByteString -> Packet+authSwitch nonce = putToPacket 2 $ do+    putWord8 0xFE+    putByteString "client_ed25519"+    putWord8 0+    putByteString nonce++okPacket :: Packet+okPacket = putToPacket 4 (putByteString "\x00\x00\x00\x02\x00\x00\x00")++-- | The packets 'completeAuth' writes while answering an AuthSwitchRequest for+-- this nonce, and whether it refused the request.+answerSwitch :: B.ByteString -> B.ByteString -> IO ([Packet], Either UnexpectedPacket ())+answerSwitch nonce pass = do+    written <- newIORef []+    replies <- Stream.fromList [okPacket]+    outcome <- try $ completeAuth replies (\p -> modifyIORef' written (p :)) pass (authSwitch nonce)+        (\_ _ _ _ -> assertFailure "ed25519 asked for full authentication")+    sent <- readIORef written+    pure (reverse sent, outcome)++newtype Nonce = Nonce B.ByteString deriving Show++instance Arbitrary Nonce where+    arbitrary = Nonce . B.pack <$> vectorOf 32 arbitrary++-- | MariaDB's server checks a reply with standard Ed25519 verification, so+-- every reply must pass it for the key MariaDB stores.+verifiesAsEd25519 :: B.ByteString -> Nonce -> Property+verifiesAsEd25519 pass (Nonce nonce) =+    either (\failure -> counterexample (show failure) False) property $ do+        key <- ed25519PublicKey pass >>= eitherCryptoError . Ed25519.publicKey+        signature <- scrambleEd25519 nonce pass >>= eitherCryptoError . Ed25519.signature+        pure (Ed25519.verify key nonce signature)++tests :: TestTree+tests = testGroup "ed25519 scramble"+    [ testCase "public keys are the ones MariaDB stores" $ do+        -- From MariaDB 11.4.9's mysql-test/suite/plugins/r/auth_ed25519.result+        -- and its ed25519_password(); the last is the account nix/ci.nix makes.+        mariadbKey <$> ed25519PublicKey "foo" @?= Right "vubFBzIrapbfHct1/J72dnUryz5VS7lA6XHH8sIx4TI"+        mariadbKey <$> ed25519PublicKey "foobar" @?= Right "qv2mG6HWCuy32Slb5xhV4THStewNz2VINVPbgk+XAJ8"+        mariadbKey <$> ed25519PublicKey "foo bar" @?= Right "Y5fV74JAVRMOK2cdnUsYS+WW9sXaaL/o+6WGKOgqnzc"+        mariadbKey <$> ed25519PublicKey "secret" @?= Right "ZIgUREUg5PVgQ6LskhXmO+eZLS0nC8be6HPjYWR4YJY"+        mariadbKey <$> ed25519PublicKey "ed25519Pass123" @?= Right "j2VKy3t+tdtEQXXes18uKRvK1XL9crguh3AregMR6kk"+      -- Signatures from ma_crypto_sign of mariadb-connector-c 3.3.5's ref10.+    , testCase "an AuthSwitchRequest is answered with libmariadb's signature, a nonce ending in 0 included" $ do+        nonce <- hex "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f00"+        signature <- hex "0c9e13ccd52e9dbb7957836f1c48ae216bf28da17c4b027f93534b0e5a348917\+                         \0096c63db819a35af8a62c976faa5bc436cb047911dcb3d28301cd07d5cd8303"+        (sent, outcome) <- answerSwitch nonce "ed25519Pass123"+        sent @?= [Packet 64 3 (L.fromStrict signature)]+        either (\(UnexpectedPacket p) -> assertFailure ("refused " ++ show p)) pure outcome+    , testCase "signature of another password and nonce" $ do+        nonce <- hex "ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100"+        signature <- hex "c0999e2c61f2fe2d679307b2b236930e1efc3680e43d4bb2b46f4250d6e11d55\+                         \047e75a777f4db9d60aa3ab64eebb3197a827df920f5c852bdd7be70ba0d6e0c"+        scrambleEd25519 nonce "secret" @?= Right signature+    , testCase "a nonce that is not 32 bytes is refused unsigned, as libmariadb does" $ do+        let request = authSwitch (B.replicate 20 7)+        (sent, outcome) <- answerSwitch (B.replicate 20 7) "ed25519Pass123"+        sent @?= []+        either (\(UnexpectedPacket p) -> p @?= request) (\() -> assertFailure "the nonce was accepted") outcome+    , testProperty "every reply verifies as Ed25519 under the stored key" verifiesAsEd25519+    ]
test/ExecuteMany.hs view
@@ -3,11 +3,9 @@  module ExecuteMany where -import           Control.Applicative import           Data.Time.Calendar  (fromGregorian) import           Data.Time.LocalTime (LocalTime (..), TimeOfDay (..)) import           Database.MySQL.Base-import qualified System.IO.Streams   as Stream import           Test.Tasty.HUnit  tests :: MySQLConn -> Assertion
+ test/FieldRoundtrip.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE TypeApplications #-}++-- | Roundtrip law for every 'Field' instance+-- (@fromField (toField x) == Right x@), plus the decode-failure behaviour+-- that a roundtrip cannot exercise.+module FieldRoundtrip (tests) where++import           Data.ByteString                    (ByteString)+import qualified Data.ByteString.Lazy               as LazyByteString+import           Data.Int                           (Int16, Int32, Int64, Int8)+import           Data.Scientific                    (Scientific)+import           Data.Text                          (Text)+import qualified Data.Text.Lazy                     as LazyText+import           Data.Time.Calendar                 (Day)+import           Data.Time.LocalTime                (LocalTime,+                                                     TimeOfDay (..))+import           Data.Word                          (Word16, Word32, Word64,+                                                     Word8)+import           Database.MySQL.Field+import           Database.MySQL.Protocol.MySQLValue (MySQLValue (..))+import           Test.QuickCheck.Instances          ()+import           Test.Tasty+import           Test.Tasty.HUnit                   (testCase, (@?=))+import           Test.Tasty.QuickCheck              (Property, testProperty,+                                                     (===))++roundtrips :: (Field a, Eq a, Show a) => a -> Property+roundtrips value = fromField (toField value) === Right value++tests :: TestTree+tests = testGroup "Field"+    [ testGroup "roundtrip"+        [ testProperty "Int8" (roundtrips @Int8)+        , testProperty "Word8" (roundtrips @Word8)+        , testProperty "Int16" (roundtrips @Int16)+        , testProperty "Word16" (roundtrips @Word16)+        , testProperty "Int32" (roundtrips @Int32)+        , testProperty "Word32" (roundtrips @Word32)+        , testProperty "Int64" (roundtrips @Int64)+        , testProperty "Word64" (roundtrips @Word64)+        , testProperty "Float" (roundtrips @Float)+        , testProperty "Double" (roundtrips @Double)+        , testProperty "Scientific" (roundtrips @Scientific)+        , testProperty "Text" (roundtrips @Text)+        , testProperty "lazy Text" (roundtrips @LazyText.Text)+        , testProperty "String" (roundtrips @String)+        , testProperty "ByteString" (roundtrips @ByteString)+        , testProperty "lazy ByteString" (roundtrips @LazyByteString.ByteString)+        , testProperty "Day" (roundtrips @Day)+        , testProperty "LocalTime" (roundtrips @LocalTime)+        , testProperty "TimeOfDay" (roundtrips @TimeOfDay)+        , testProperty "Bool" (roundtrips @Bool)+        , testProperty "Maybe Int32" (roundtrips @(Maybe Int32))+        , testProperty "Maybe Text" (roundtrips @(Maybe Text))+        ]+    , testGroup "decode failures"+        [ testCase "NULL into a non-Maybe type is DecodeUnexpectedNull" $+            decodeText MySQLNull+                @?= Left (DecodeUnexpectedNull (ExpectedTypeName "Text"))+        , testCase "wrong constructor is DecodeTypeMismatch" $+            decodeInt8 (MySQLText "hi")+                @?= Left (DecodeTypeMismatch (ExpectedTypeName "Int8")+                                             (MySQLText "hi"))+        , testCase "no implicit widening: INT32 value into Int64 fails" $+            decodeInt64 (MySQLInt32 42)+                @?= Left (DecodeTypeMismatch (ExpectedTypeName "Int64")+                                             (MySQLInt32 42))+        , testCase "negative TIME into TimeOfDay fails" $+            decodeTimeOfDay (MySQLTime 1 (TimeOfDay 1 2 3))+                @?= Left (DecodeTypeMismatch (ExpectedTypeName "TimeOfDay")+                                             (MySQLTime 1 (TimeOfDay 1 2 3)))+        ]+    , testGroup "decode alternatives"+        [ testCase "NULL into Maybe is Nothing" $+            decodeMaybe @Int32 MySQLNull @?= Right Nothing+        , testCase "TIMESTAMP decodes into LocalTime" $+            decodeLocalTime (MySQLTimeStamp (read "2026-07-21 12:00:00"))+                @?= Right (read "2026-07-21 12:00:00")+        , testCase "nonzero TINYINT decodes as True" $+            decodeBool (MySQLInt8 5) @?= Right True+        , testCase "unsigned TINYINT decodes into Bool" $+            decodeBool (MySQLInt8U 0) @?= Right False+        , testCase "nested Maybe collapses Just Nothing to Nothing (documented NULL lossiness)" $+            fromField @(Maybe (Maybe Int32)) (toField (Just (Nothing :: Maybe Int32)))+                @?= Right Nothing+        , testCase "NaN encodes losslessly (roundtrip == fails only since NaN /= NaN)" $+            fmap isNaN (decodeDouble (encodeDouble (0 / 0))) @?= Right True+        , testCase "lone surrogate in String is rejected by encodeString" $+            encodeString ['\xD800'] @?= Left (EncodeStringLoneSurrogate ['\xD800'])+        ]+    ]
+ test/FloatingPoint.hs view
@@ -0,0 +1,40 @@+-- | FLOAT and DOUBLE values MySQL writes in exponent form. The text protocol+-- sends large ones as, for example, @1e20@; reading them as plain decimals+-- stopped at the @e@ and gave 1.+module FloatingPoint (tests) where++import           Database.MySQL.Base+import           QueryApi+import qualified System.IO.Streams as Stream+import           Test.Tasty+import           Test.Tasty.HUnit++exponentForms :: Query+exponentForms =+    "SELECT CAST(1e20 AS DOUBLE), CAST(-1e20 AS DOUBLE), CAST(1.5e300 AS DOUBLE), \+    \CAST(2.5e-7 AS DOUBLE), CAST(1e20 AS FLOAT)"++expected :: [MySQLValue]+expected = [MySQLDouble 1e20, MySQLDouble (-1e20), MySQLDouble 1.5e300, MySQLDouble 2.5e-7, MySQLFloat 1e20]++tests :: QueryApi -> TestTree+tests api = testGroup "FLOAT and DOUBLE in exponent form"+    [ testCase "text protocol" $ withConnection $ \c -> do+        (_, rows) <- apiQuery_ api (ColumnCount 5) c exponentForms+        values <- Stream.toList rows+        assertEqual "values" [expected] values+    , testCase "binary protocol" $ withConnection $ \c -> do+        stmt <- prepareStmt c exponentForms+        (_, rows) <- apiQueryStmt api (ColumnCount 5) c stmt []+        values <- Stream.toList rows+        assertEqual "values" [expected] values+    ]++withConnection :: (MySQLConn -> Assertion) -> Assertion+withConnection body = do+    (_, c) <- connectDetail defaultConnectInfo+        { ciUser = "testMySQLHaskell"+        , ciDatabase = "testMySQLHaskell"+        }+    body c+    close c
+ test/FloatingPointText.hs view
@@ -0,0 +1,59 @@+-- | 'readDouble' and 'readFloat' must give the nearest value, as 'read' does,+-- for every way MySQL writes a FLOAT or DOUBLE: plain decimals, and exponent+-- form for large values, whether the fast path or the exact one reads them.+module FloatingPointText (tests) where++import qualified Data.ByteString.Char8                 as BC+import           Database.MySQL.Protocol.FloatingPoint (readDouble, readFloat)+import           Numeric                               (showFFloat)+import           Test.QuickCheck+import           Test.Tasty+import           Test.Tasty.HUnit                      (testCase, (@?=))+import           Test.Tasty.QuickCheck                 (testProperty)++-- | The properties run 5,000 cases each: CI uses QuickCheck's default of 100,+-- which missed a fast path that accepted truncated significands.+tests :: TestTree+tests = testGroup "FLOAT and DOUBLE text"+    [ testProperty "a shown Double reads back as itself" $ withMaxSuccess 5000 $+        forAll finiteDouble $ \value -> readDouble (BC.pack (show value)) === Just value+    , testProperty "a Double in plain decimals reads back as itself" $ withMaxSuccess 5000 $+        forAll finiteDouble $ \value -> readDouble (BC.pack (showFFloat Nothing value "")) === Just value+    , testProperty "a shown Float reads back as itself" $ withMaxSuccess 5000 $+        forAll finiteFloat $ \value -> readFloat (BC.pack (show value)) === Just value+    , testProperty "any decimal text gives the Double read gives" $ withMaxSuccess 5000 $+        forAll genDecimalText $ \text -> readDouble (BC.pack text) === Just (read text)+    , testProperty "any decimal text gives the Float read gives" $ withMaxSuccess 5000 $+        forAll genDecimalText $ \text -> readFloat (BC.pack text) === Just (read text)+    , testCase "exponent form, as MySQL writes large values" $+        map (readDouble . BC.pack) ["1e20", "-1e20", "1.5e300", "2.5E-7", "1e+5"]+            @?= map Just [1e20, -1e20, 1.5e300, 2.5e-7, 1e5]+    , testCase "seventeen significant digits" $+        readDouble (BC.pack "0.30000000000000004") @?= Just (0.1 + 0.2)+    , testCase "more than 19 significant digits, the first 19 an exact Double" $+        map (readDouble . BC.pack) ["1000000000000000000000", "10000000000000000000000.5", "-20840713482839362564625e5"]+            @?= map Just [1e21, 1e22, -2.0840713482839362564625e27]+    , testCase "out of range" $+        map (readDouble . BC.pack) ["1e400", "1e-400"] @?= map Just [1 / 0, 0]+    , testCase "bytes after the number are ignored, as readDecimal ignored them" $+        map (readDouble . BC.pack) ["12abc", "3.5e", "7.", "1e5x"] @?= map Just [12, 3.5, 7, 1e5]+    , testCase "text that does not start with a number" $+        map (readDouble . BC.pack) ["", "abc", ".5", "-", "e5"] @?= replicate 5 Nothing+    ]++finiteDouble :: Gen Double+finiteDouble = arbitrary `suchThat` (\value -> not (isNaN value || isInfinite value))++finiteFloat :: Gen Float+finiteFloat = arbitrary `suchThat` (\value -> not (isNaN value || isInfinite value))++-- | A sign, 1 to 25 digits, an optional fraction and an optional exponent from+-- -330 to 330: short numbers take the fast path, long ones and large+-- exponents the exact one.+genDecimalText :: Gen String+genDecimalText = do+    sign <- elements ["", "-"]+    integerDigits <- choose (1, 25) >>= flip vectorOf (elements ['0' .. '9'])+    fraction <- oneof [pure "", ('.' :) <$> (choose (1, 20) >>= flip vectorOf (elements ['0' .. '9']))]+    exponentPart <- oneof [pure "", ('e' :) . show <$> choose (-330, 330 :: Int)]+    pure (sign ++ integerDigits ++ fraction ++ exponentPart)
+ test/HandshakeGolden.hs view
@@ -0,0 +1,95 @@+module HandshakeGolden (tests) where++import qualified Data.Binary                as Binary+import           Data.Bits                  ((.|.))+import qualified Data.ByteString            as B+import qualified Data.ByteString.Base16     as Hex+import qualified Data.ByteString.Char8      as BC+import qualified Data.ByteString.Lazy       as L+import           Database.MySQL.Connection  (handshakeResponsePacket, sslRequestPacket)+import           Database.MySQL.Protocol.Auth+import           Database.MySQL.Protocol.Packet (Packet, getFromPacket, putToPacket)+import           Data.Binary.Put            (putByteString)+import           Test.Tasty+import           Test.Tasty.HUnit++-- Greetings captured from the servers nix/ci.nix tests against, and the+-- packets mysql-haskell 1.3.3 (92a9401) answered them with for user and+-- schema testMySQLHaskell, password "secret" and charset 33.+mariadbGreeting, mysqlGreeting :: B.ByteString+mariadbGreeting = hex "0a31312e342e392d4d6172696144422d6c6f67000a00000068513b714856583500feffe00200ff81150000000000001d0000003e78646c727473657e6b7a47006d7973716c5f6e61746976655f70617373776f726400"+mysqlGreeting = hex "0a382e302e3435001200000073454f634d16264e00ffffff0200ffdf15000000000000000000005d2658501266396b0d7546110063616368696e675f736861325f70617373776f726400"++masterAuthMariaDB, masterTLSAuthMariaDB, masterAuthMySQL, masterTLSAuthMySQL, masterSSLRequest :: B.ByteString+masterAuthMariaDB = hex "6d0000010da30b00ffffff00210000000000000000000000000000000000000000000000746573744d7953514c4861736b656c6c00148993d478ffb390507c14ce8d6da10baff99ac06c746573744d7953514c4861736b656c6c006d7973716c5f6e61746976655f70617373776f726400"+masterTLSAuthMariaDB = hex "6d0000020da30b00ffffff00210000000000000000000000000000000000000000000000746573744d7953514c4861736b656c6c00148993d478ffb390507c14ce8d6da10baff99ac06c746573744d7953514c4861736b656c6c006d7973716c5f6e61746976655f70617373776f726400"+masterAuthMySQL = hex "790000010da30b00ffffff00210000000000000000000000000000000000000000000000746573744d7953514c4861736b656c6c00208d6350e1245ca38229fd5cc86234ea67d2d19056795007230dbec8bd483e4b89746573744d7953514c4861736b656c6c0063616368696e675f736861325f70617373776f726400"+masterTLSAuthMySQL = hex "790000020da30b00ffffff00210000000000000000000000000000000000000000000000746573744d7953514c4861736b656c6c00208d6350e1245ca38229fd5cc86234ea67d2d19056795007230dbec8bd483e4b89746573744d7953514c4861736b656c6c0063616368696e675f736861325f70617373776f726400"+masterSSLRequest = hex "200000010dab0b00ffffff00210000000000000000000000000000000000000000000000"++hex :: String -> B.ByteString+hex = either error id . Hex.decode . BC.pack++parseGreeting :: B.ByteString -> IO (Greeting, ExtendedCapabilities)+parseGreeting = getFromPacket getGreetingExtended . putToPacket 0 . putByteString++wire :: Packet -> B.ByteString+wire = L.toStrict . Binary.encode++-- | Every byte one handshake sends: SSL request, response after it, and the+-- response without TLS.+handshakeBytes :: ExtendedCapabilities -> (Greeting, ExtendedCapabilities) -> (B.ByteString, B.ByteString, B.ByteString)+handshakeBytes requested (greeting, offered) =+    let negotiated = negotiateExtended requested greeting offered+        response seqN = wire (handshakeResponsePacket seqN negotiated "testMySQLHaskell" "testMySQLHaskell" "secret" 33 greeting)+    in (wire (sslRequestPacket negotiated 33), response 2, response 1)++extendedMetadata, everyCapability :: ExtendedCapabilities+extendedMetadata = ExtendedCapabilities 0x08+everyCapability = ExtendedCapabilities 0xffffffff++tests :: TestTree+tests = testGroup "handshake"+    [ testCase "greetings: MariaDB offers extended capabilities, MySQL none" $ do+        (_, mariadbOffer) <- parseGreeting mariadbGreeting+        (_, mysqlOffer) <- parseGreeting mysqlGreeting+        mariadbOffer @?= ExtendedCapabilities 0x1d+        mysqlOffer @?= noExtendedCapabilities+    , testCase "MariaDB is told apart by its greeting's capabilities, not its version string" $ do+        (mariadb, _) <- parseGreeting mariadbGreeting+        (mysql, _) <- parseGreeting mysqlGreeting+        offersExtendedCapabilities mariadb @?= True+        offersExtendedCapabilities mysql @?= False+        -- mariadbd --version=8.0.36 sends exactly this greeting.+        offersExtendedCapabilities mariadb {greetingVersion = "8.0.36"} @?= True+    , testCase "MySQL: 1.3.3's bytes whatever is requested" $ do+        greeting <- parseGreeting mysqlGreeting+        let master = (masterSSLRequest, masterTLSAuthMySQL, masterAuthMySQL)+        handshakeBytes noExtendedCapabilities greeting @?= master+        handshakeBytes extendedMetadata greeting @?= master+        handshakeBytes everyCapability greeting @?= master+    , testCase "MariaDB, nothing requested: 1.3.3's bytes" $ do+        greeting <- parseGreeting mariadbGreeting+        handshakeBytes noExtendedCapabilities greeting+            @?= (masterSSLRequest, masterTLSAuthMariaDB, masterAuthMariaDB)+    , testCase "MariaDB, a capability it does not offer: 1.3.3's bytes" $ do+        greeting <- parseGreeting mariadbGreeting+        handshakeBytes (ExtendedCapabilities 0x02) greeting+            @?= (masterSSLRequest, masterTLSAuthMariaDB, masterAuthMariaDB)+    , testCase "a server setting CLIENT_MYSQL gets 1.3.3's bytes, whatever it offers" $ do+        (greeting, offered) <- parseGreeting mariadbGreeting+        let mysqlFlagged = greeting { greetingCaps = greetingCaps greeting .|. 1 }+        handshakeBytes extendedMetadata (mysqlFlagged, offered)+            @?= (masterSSLRequest, masterTLSAuthMariaDB, masterAuthMariaDB)+    , testCase "MariaDB, extended metadata: CLIENT_MYSQL cleared, bit 35 sent" $ do+        greeting <- parseGreeting mariadbGreeting+        let (ssl, tlsAuth, auth) = handshakeBytes extendedMetadata greeting+            -- 4 header bytes, then capabilities; the extended capabilities+            -- are the last four of the 23 reserved bytes.+            expect master = B.concat+                [ B.take 4 master, hex "0ca30b00", B.take 24 (B.drop 8 master)+                , hex "08000000", B.drop 36 master ]+        auth @?= expect masterAuthMariaDB+        tlsAuth @?= expect masterTLSAuthMariaDB+        ssl @?= B.concat [B.take 4 masterSSLRequest, hex "0cab0b00", B.take 24 (B.drop 8 masterSSLRequest), hex "08000000"]+    ]
+ test/Integration.hs view
@@ -0,0 +1,89 @@+module Main (main) where++import qualified Data.ByteString as B+import           Database.MySQL.Base+import           System.Environment (lookupEnv)+import           Test.Tasty (TestTree, defaultMain, testGroup)+import qualified AbandonedReplies+import qualified BinLogRowEvents+import qualified CABundle+import qualified CachingSha2+import qualified FloatingPoint+import qualified MariaDBNegotiation+import qualified LargeRows+import qualified Latin1Results+import qualified NonUtf8Results+import qualified ParameterEscaping+import qualified PlaceholderLiterals+import qualified UnsignedInsertId+import qualified ZeroDateRoundtrip+import qualified MultipleResults+import qualified MysqlTests+import           QueryApi+import qualified QueryWithoutResultSet+import qualified ResetConnection+import qualified RoundtripBit+import qualified RoundtripYear+import qualified RowDecoderQueries+import qualified SelectOne+import qualified TLSClientCertificate+import qualified TLSConnection+import qualified UnixSocket++main :: IO ()+main = do+    (greet, c) <- connectDetail defaultConnectInfo+        {ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell"}+    close c+    let ver = greetingVersion greet+        isMySql80 = "8." `B.isPrefixOf` ver+                 || "9." `B.isPrefixOf` ver++    -- Probe for a Unix domain socket (MYSQL_UNIX_SOCKET env var, then common paths).+    mSockPath <- UnixSocket.findSocketPath++    -- Probe for TLS CA certificate path (set in NixOS VM tests).+    mTlsCaPath <- lookupEnv "MYSQL_TLS_CA_PATH"++    defaultMain $ testGroup "mysql-integration" $+        [ MysqlTests.tests+        , MultipleResults.tests+        , RowDecoderQueries.tests+        , MariaDBNegotiation.tests greet+        , NonUtf8Results.tests+        , UnsignedInsertId.tests+        , BinLogRowEvents.tests+        , CABundle.tests+        , ZeroDateRoundtrip.typedDecoderTests+        , ResetConnection.tests+        ]+        -- binlog_transaction_compression is MySQL 8.0.20+ only.+        ++ [ BinLogRowEvents.compressedTransactionTests | isMySql80 ]+        ++ maybe [] (pure . TLSClientCertificate.tests) mTlsCaPath+        ++ map (apiTests isMySql80 mSockPath mTlsCaPath) queryApis+        -- The typed decoders give a date a 'Day' cannot hold as an error instead+        -- of a value to write back, which 'ZeroDateRoundtrip.typedDecoderTests' checks.+        ++ map ZeroDateRoundtrip.tests mysqlValueApis++-- | The suites that read rows, through one of the 'QueryApi's.+apiTests :: Bool -> Maybe FilePath -> Maybe FilePath -> QueryApi -> TestTree+apiTests isMySql80 mSockPath mTlsCaPath api = testGroup (apiName api) $+    [ SelectOne.tests api+    , RoundtripBit.tests api+    , RoundtripYear.tests api+    , QueryWithoutResultSet.tests api+    , MultipleResults.rowTests api+    , FloatingPoint.tests api+    , LargeRows.tests api+    , PlaceholderLiterals.tests api+    , ParameterEscaping.tests api+    , Latin1Results.tests api+    , AbandonedReplies.tests api+    ]+    -- caching_sha2_password is MySQL 8.0+ only (MariaDB does not support it).+    -- The sha2 test users are created by the nix CI config for the MySQL 8.0 VM.+    ++ [ CachingSha2.tests api | isMySql80 ]+    -- Unix socket tests are included only when a socket file is found.+    ++ maybe [] (\p -> [UnixSocket.tests api p]) mSockPath+    -- TLS tests are included only when a CA certificate path is provided.+    ++ maybe [] (\p -> [TLSConnection.tests api p]) mTlsCaPath
+ test/IntegrationMariaDB.hs view
@@ -0,0 +1,21 @@+-- | Tests of "Database.MariaDB" that need a MariaDB server; nix/ci.nix+-- runs them in the MariaDB VM only. Capability negotiation against MySQL is+-- covered by the shared integration suite.+module Main (main) where++import           System.Environment (lookupEnv)+import           Test.Tasty (defaultMain, testGroup)+import qualified MariaDBBulk+import qualified MariaDBEd25519+import qualified MariaDBExtendedMetadata+import qualified UnixSocket++main :: IO ()+main = do+    socketPath <- UnixSocket.findSocketPath+    caPath <- lookupEnv "MYSQL_TLS_CA_PATH"+    defaultMain $ testGroup "mariadb-integration"+        [ MariaDBExtendedMetadata.tests socketPath caPath+        , MariaDBBulk.tests+        , MariaDBEd25519.tests socketPath caPath+        ]
+ test/JSON.hs view
@@ -0,0 +1,32 @@+module JSON where++import Control.Monad+import qualified Data.ByteString as B+import Test.Tasty.HUnit+import Test.Tasty (TestTree)+import System.Directory (getDirectoryContents, doesDirectoryExist)+import System.FilePath ((</>))+import qualified Aeson as A+import qualified Data.Attoparsec.ByteString as A+import qualified AesonBP as B+import qualified Data.Binary.Parser as B+import Data.List+++pathTo :: String -> IO FilePath+pathTo wat = do+  exists <- doesDirectoryExist "test"+  return $ if exists+           then "test" </> wat+           else wat++tests :: IO [TestTree]+tests = do+    path <- pathTo "json-data"+    names <- sort . filter (`notElem` [".", ".."]) <$> getDirectoryContents path+    forM names $ \name -> do+        bs <- B.readFile (path </> name)+        return $ testCase name $+            assertEqual name (A.parseOnly A.jsonEOF' bs) (B.parseOnly B.jsonEOF' bs)++
+ test/LargeRows.hs view
@@ -0,0 +1,28 @@+-- | Text-protocol rows whose first value is 16 MiB or longer: its length+-- prefix starts with the same byte as an EOF packet, so the row was taken for+-- the end of the result. Binary-protocol rows start with 0x00 and never were.+module LargeRows (tests) where++import qualified Data.ByteString.Lazy.Char8 as LC+import qualified Data.Text                  as T+import           Database.MySQL.Base+import           QueryApi+import qualified System.IO.Streams          as Stream+import           Test.Tasty+import           Test.Tasty.HUnit+import           TestUser                   (withTestConnection)++tests :: QueryApi -> TestTree+tests api = testGroup "values of 16 MiB and longer" (map (firstValueOf api) [16777216, 16777217, 33554430])++firstValueOf :: QueryApi -> Int -> TestTree+firstValueOf api size = testCase (show size ++ " bytes") $ withTestConnection $ \c -> do+    (_, rows) <- apiQuery_ api (ColumnCount 2) c (Query (LC.pack ("SELECT REPEAT('a', " ++ show size ++ "), 'after'")))+    Stream.toList rows >>= assertOneRow size++assertOneRow :: Int -> [[MySQLValue]] -> Assertion+assertOneRow size rows = case rows of+    [[MySQLText value, MySQLText after]] -> do+        assertEqual "length of the long value" size (T.length value)+        assertEqual "value after it" "after" after+    other -> assertFailure ("rows: " ++ take 200 (show other))
+ test/Latin1Results.hs view
@@ -0,0 +1,36 @@+-- | Results in latin1, as after @SET NAMES latin1@. Their bytes used to be+-- decoded as UTF-8, which threw on anything outside ASCII.+module Latin1Results (tests) where++import           Control.Monad       (void)+import           Database.MySQL.Base+import           QueryApi+import qualified System.IO.Streams   as Stream+import           Test.Tasty+import           Test.Tasty.HUnit+import           TestUser            (withTestConnection)++tests :: QueryApi -> TestTree+tests api = testCase "latin1 results decode to the characters the server means" $+    withTestConnection $ \c -> do+        void (execute_ c "CREATE TEMPORARY TABLE latin1_results (b VARCHAR(200) CHARACTER SET latin1)")+        -- Every byte from 0x80 up, the part of latin1 that is not ASCII.+        void (execute_ c "INSERT INTO latin1_results VALUES (CONVERT(UNHEX(CONCAT(\+                         \'808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9F',\+                         \'A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF',\+                         \'C0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF',\+                         \'E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF')) USING latin1))")+        serverUtf8 <- selectAll c+        void (execute_ c "SET NAMES latin1")+        (_, latin1Rows) <- apiQuery_ api (ColumnCount 1) c "SELECT b FROM latin1_results"+        textRows <- Stream.toList latin1Rows+        assertEqual "text protocol" serverUtf8 textRows+        stmt <- prepareStmt c "SELECT b FROM latin1_results"+        (_, rows) <- apiQueryStmt api (ColumnCount 1) c stmt []+        binaryRows <- Stream.toList rows+        assertEqual "binary protocol" serverUtf8 binaryRows++selectAll :: MySQLConn -> IO [[MySQLValue]]+selectAll c = do+    (_, rows) <- query_ c "SELECT b FROM latin1_results"+    Stream.toList rows
test/Main.hs view
@@ -1,185 +1,59 @@-{-# LANGUAGE ScopedTypeVariables #-}--module Main where+module Main (main) where -import qualified BinaryRow-import qualified BinaryRowNew-import qualified BinLog-import qualified BinLogNew-import           Control.Concurrent    (forkIO, threadDelay)-import           Control.Exception     (bracket, catch)-import           Control.Monad-import qualified Data.ByteString       as B-import           Database.MySQL.Base-import           Database.MySQL.BinLog-import           System.Environment-import qualified System.IO.Streams as Stream-import           Test.Tasty-import           Test.Tasty.HUnit-import qualified TextRow-import qualified ExecuteMany-import qualified TextRowNew+import qualified QC.ByteString as ByteString+import qualified QC.Combinator as Combinator+import Test.Tasty (defaultMain, testGroup)+import qualified JSON+import qualified Sha1Scramble+import qualified Sha256Scramble+import qualified Ed25519Scramble+import qualified StringLiterals+import qualified Word24+import qualified TCPStreams+import qualified BoundsCheck+import qualified FieldRoundtrip+import qualified TextRowDecoding+import qualified DirectDecoding+import qualified DecoderRoundtrip+import qualified FloatingPointText+import qualified HandshakeGolden+import qualified BinLogChecksum+import qualified BinLogEventCodes+import qualified PacketClassification+import qualified Placeholders+import qualified ResultCharsets+import qualified ZeroDates  main :: IO ()--main = defaultMain $ testCaseSteps "mysql-haskell test suit" $ \step -> do--    step "preparing table..."-    (greet, c) <- connectDetail defaultConnectInfo {ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell"}--    let ver = greetingVersion greet-        isNew = "5.6" `B.isPrefixOf` ver-                || "5.7" `B.isPrefixOf` ver  -- from MySQL 5.6.4 and up-                                             -- TIME, DATETIME, and TIMESTAMP support fractional seconds---    execute_ c "DROP TABLE IF EXISTS test"-    execute_ c "DROP TABLE IF EXISTS test_new"--    execute_ c  "CREATE TABLE test(\-                \__id           INT,\-                \__bit          BIT(16),\-                \__tinyInt      TINYINT,\-                \__tinyIntU     TINYINT UNSIGNED,\-                \__smallInt     SMALLINT,\-                \__smallIntU    SMALLINT UNSIGNED,\-                \__mediumInt    MEDIUMINT,\-                \__mediumIntU   MEDIUMINT UNSIGNED,\-                \__int          INT,\-                \__intU         INT UNSIGNED,\-                \__bigInt       BIGINT,\-                \__bigIntU      BIGINT UNSIGNED,\-                \__decimal      DECIMAL(20,10),\-                \__float        FLOAT,\-                \__double       DOUBLE,\-                \__date         DATE,\-                \__datetime     DATETIME,\-                \__timestamp    TIMESTAMP NULL,\-                \__time         TIME,\-                \__year         YEAR(4),\-                \__char         CHAR(8),\-                \__varchar      VARCHAR(1024),\-                \__binary       BINARY(8),\-                \__varbinary    VARBINARY(1024),\-                \__tinyblob     TINYBLOB,\-                \__tinytext     TINYTEXT,\-                \__blob         BLOB(1000000),\-                \__text         TEXT(1000000),\-                \__enum         ENUM('foo', 'bar', 'qux'),\-                \__set          SET('foo', 'bar', 'qux')\-                \) CHARACTER SET utf8"--    resetTestTable c--    step "testing executeMany"-    ExecuteMany.tests c--    resetTestTable c--    step "testing text protocol"-    TextRow.tests c--    resetTestTable c--    step "testing binary protocol"-    BinaryRow.tests c--    resetTestTable c---    when isNew $ do-        execute_ c "CREATE TABLE test_new(\-                   \__id           INT,\-                   \__datetime     DATETIME(2),\-                   \__timestamp    TIMESTAMP(4) NULL,\-                   \__time         TIME(6)\-                   \) CHARACTER SET utf8"--        resetTest57Table c--        step "testing MySQL5.7 extra text protocol"-        TextRowNew.tests c--        resetTest57Table c--        step "testing MySQL5.7 extra binary protocol"-        BinaryRowNew.tests c--        void $ resetTest57Table c--    step "testing binlog protocol"--    if isNew-    then do-        forkIO BinLogNew.eventProducer-        BinLogNew.tests c-    else do-        forkIO BinLog.eventProducer-        BinLog.tests c--    close c--    (greet, c) <- connectDetail defaultConnectInfo {ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell"}-    execute_ c "SET PASSWORD = PASSWORD('123456abcdefg???')"-    close c--    let loginFailMsg = "ERRException (ERR {errCode = 1045, errState = \"28000\", \-            \errMsg = \"Access denied for user 'testMySQLHaskell'@'localhost' (using password: YES)\"})"--    (greet, c) <- connectDetail-        defaultConnectInfo {ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell", ciPassword = "123456abcdefg???"}-    execute_ c "SET PASSWORD = PASSWORD('')"-    close c--    catch-        (void $ connectDetail-                    defaultConnectInfo-                        {ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell", ciPassword = "wrongPassWord"})-        (\ (e :: ERRException) -> assertEqual "wrong password should fail to login" (show e) loginFailMsg)--  where-    resetTestTable c = do-            execute_ c  "DELETE FROM test WHERE __id=0"-            execute_ c  "INSERT INTO test VALUES(\-                    \0,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL,\-                    \NULL\-                    \)"--    resetTest57Table c = do-            execute_ c  "DELETE FROM test_new WHERE __id=0"-            execute_ c  "INSERT INTO test_new VALUES(\-                        \0,\-                        \NULL,\-                        \NULL,\-                        \NULL\-                        \)"--+main = do+    jsonTests <- JSON.tests+    defaultMain $ testGroup "tests" [+      testGroup "binary-parser" [+        testGroup "bs" ByteString.tests+      , testGroup "combinator" Combinator.tests+      , testGroup "JSON" jsonTests+      ],+      testGroup "word24"+         Word24.tests+      , testGroup "tcp-streams"+         [+           TCPStreams.tests+         ]+      , Sha1Scramble.tests+      , Sha256Scramble.tests+      , Ed25519Scramble.tests+      , BoundsCheck.tests+      , FieldRoundtrip.tests+      , TextRowDecoding.tests+      , DirectDecoding.tests+      , DecoderRoundtrip.tests+      , FloatingPointText.tests+      , HandshakeGolden.tests+      , PacketClassification.tests+      , Placeholders.tests+      , StringLiterals.tests+      , ZeroDates.tests+      , ResultCharsets.tests+      , BinLogEventCodes.tests+      , BinLogChecksum.tests+      ]
+ test/MariaDBBulk.hs view
@@ -0,0 +1,82 @@+module MariaDBBulk (tests) where++import           Control.Exception      (try)+import           Data.List.NonEmpty     (NonEmpty ((:|)))+import qualified Data.List.NonEmpty     as NonEmpty+import qualified Data.Text              as T+import           Database.MySQL.Base+import           Database.MariaDB+import qualified System.IO.Streams      as Stream+import           Test.Tasty+import           Test.Tasty.HUnit++testConnectInfo :: ConnectInfo+testConnectInfo = defaultConnectInfo {ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell"}++withBulkConnection :: (MariaDBConn -> MySQLConn -> IO a) -> IO a+withBulkConnection action = do+    conn <- connectMariaDB [MariaDBBulkOperations] testConnectInfo+    mariadbCapabilities conn @?= [MariaDBBulkOperations]+    result <- action conn (mysqlConnection conn)+    close (mysqlConnection conn)+    pure result++selectRows :: MySQLConn -> Query -> IO [[MySQLValue]]+selectRows conn sql = query_ conn sql >>= Stream.toList . snd++thousandRows :: NonEmpty [BulkParam]+thousandRows = NonEmpty.fromList+    (map (\n -> [BulkValue (MySQLInt32 n), BulkValue (MySQLText (T.pack ("row " ++ show n)))]) [1 .. 1000])++tests :: TestTree+tests = testGroup "MariaDB bulk execution"+    [ testCase "inserts every row in one command" $ withBulkConnection $ \conn mysqlConn -> do+        _ <- execute_ mysqlConn "CREATE TEMPORARY TABLE bulk_rows (n INT, s VARCHAR(16))"+        statement <- prepareStmt mysqlConn "INSERT INTO bulk_rows VALUES (?, ?)"+        ok <- executeBulk conn statement thousandRows+        okAffectedRows ok @?= 1000+        counts <- selectRows mysqlConn "SELECT COUNT(*), SUM(n), COUNT(DISTINCT s) FROM bulk_rows"+        counts @?= [[MySQLInt64 1000, MySQLDecimal 500500, MySQLInt64 1000]]+        sample <- selectRows mysqlConn "SELECT n, s FROM bulk_rows WHERE n IN (1, 1000) ORDER BY n"+        sample @?= [[MySQLInt32 1, MySQLText "row 1"], [MySQLInt32 1000, MySQLText "row 1000"]]+    , testCase "NULL, DEFAULT and IGNORE indicators" $ withBulkConnection $ \conn mysqlConn -> do+        _ <- execute_ mysqlConn "CREATE TEMPORARY TABLE bulk_indicators \+            \(id INT PRIMARY KEY, a INT DEFAULT 7, b VARCHAR(8) DEFAULT 'dflt', c INT NULL)"+        insert <- prepareStmt mysqlConn "INSERT INTO bulk_indicators VALUES (?, ?, ?, ?)"+        _ <- executeBulk conn insert $+               [BulkValue (MySQLInt32 1), BulkValue (MySQLInt32 1), BulkValue (MySQLText "x"), BulkValue MySQLNull]+            :| [[BulkValue (MySQLInt32 2), BulkDefault, BulkIgnore, BulkValue (MySQLInt32 3)]]+        inserted <- selectRows mysqlConn "SELECT id, a, b, c FROM bulk_indicators ORDER BY id"+        inserted @?=+            [ [MySQLInt32 1, MySQLInt32 1, MySQLText "x", MySQLNull]+            , [MySQLInt32 2, MySQLInt32 7, MySQLText "dflt", MySQLInt32 3] ]+        update <- prepareStmt mysqlConn "UPDATE bulk_indicators SET a = ?, b = ? WHERE id = ?"+        ok <- executeBulk conn update $+               [BulkIgnore, BulkValue (MySQLText "y"), BulkValue (MySQLInt32 1)]+            :| [[BulkValue (MySQLInt32 9), BulkIgnore, BulkValue (MySQLInt32 2)]]+        okAffectedRows ok @?= 2+        updated <- selectRows mysqlConn "SELECT id, a, b FROM bulk_indicators ORDER BY id"+        updated @?=+            [ [MySQLInt32 1, MySQLInt32 1, MySQLText "y"]+            , [MySQLInt32 2, MySQLInt32 9, MySQLText "dflt"] ]+    , testCase "rows the command cannot carry are refused before sending" $ withBulkConnection $ \conn mysqlConn -> do+        _ <- execute_ mysqlConn "CREATE TEMPORARY TABLE bulk_refused (n INT)"+        statement <- prepareStmt mysqlConn "INSERT INTO bulk_refused VALUES (?)"+        mixed <- try (executeBulk conn statement ([BulkValue (MySQLInt32 1)] :| [[BulkValue (MySQLText "2")]]))+        mixed @?= Left (BulkMixedTypes 0 [MySQLInt32 1, MySQLText "2"])+        ragged <- try (executeBulk conn statement ([BulkValue (MySQLInt32 1)] :| [[]]))+        ragged @?= Left (BulkRowWidth 1 1 0)+        empty <- try (executeBulk conn statement ([] :| []))+        empty @?= Left BulkNoParameters+        stored <- selectRows mysqlConn "SELECT COUNT(*) FROM bulk_refused"+        stored @?= [[MySQLInt64 0]]+    , testCase "a server error leaves the connection usable" $ withBulkConnection $ \conn mysqlConn -> do+        _ <- execute_ mysqlConn "CREATE TEMPORARY TABLE bulk_unique (n INT PRIMARY KEY)"+        statement <- prepareStmt mysqlConn "INSERT INTO bulk_unique VALUES (?)"+        duplicate <- try (executeBulk conn statement ([BulkValue (MySQLInt32 1)] :| [[BulkValue (MySQLInt32 1)]]))+        case duplicate of+            Left (ERRException err) -> errCode err @?= 1062+            Right ok -> assertFailure ("duplicate key accepted: " ++ show ok)+        rows <- selectRows mysqlConn "SELECT 'still usable'"+        rows @?= [[MySQLText "still usable"]]+    ]
+ test/MariaDBEd25519.hs view
@@ -0,0 +1,44 @@+-- | Logins as an account identified via MariaDB's ed25519 plugin, which+-- nix/ci.nix creates in the MariaDB VM. The server greets with+-- mysql_native_password and switches the client to client_ed25519 once it+-- knows the account.+module MariaDBEd25519 (tests) where++import           Database.MySQL.Base+import qualified Database.MySQL.TLS  as TLS+import qualified System.IO.Streams   as Stream+import           Test.Tasty+import           Test.Tasty.HUnit++ed25519ConnectInfo :: ConnectInfo+ed25519ConnectInfo = defaultConnectInfo+    { ciUser = "testMySQLHaskellEd25519"+    , ciPassword = "ed25519Pass123"+    , ciDatabase = "testMySQLHaskell"+    }++-- | The account the server authenticated, so a login that fell back to some+-- other account would not pass.+assertEd25519Account :: MySQLConn -> Assertion+assertEd25519Account conn = do+    (_, rows) <- query_ conn "SELECT CURRENT_USER()"+    users <- Stream.toList rows+    users @?= [[MySQLText "testMySQLHaskellEd25519@localhost"]]+    close conn++tests :: Maybe FilePath -> Maybe FilePath -> TestTree+tests socketPath caPath = testGroup "ed25519 authentication" $+    [ testCase "logs in over TCP" $+        connect ed25519ConnectInfo >>= assertEd25519Account+    ]+    ++ maybe [] (pure . unixSocketTest) socketPath+    ++ maybe [] (pure . tlsTest) caPath++unixSocketTest :: FilePath -> TestTree+unixSocketTest path = testCase "logs in over a Unix socket" $+    connectUnixSocket path ed25519ConnectInfo >>= assertEd25519Account++tlsTest :: FilePath -> TestTree+tlsTest caPath = testCase "logs in over TLS" $ do+    cparams <- TLS.makeClientParams (TLS.CustomCAStore caPath)+    TLS.connect ed25519ConnectInfo (cparams, "Winter") >>= assertEd25519Account
+ test/MariaDBExtendedMetadata.hs view
@@ -0,0 +1,96 @@+module MariaDBExtendedMetadata (tests) where++import qualified Data.ByteString        as B+import           Database.MySQL.Base+import           Database.MariaDB+import qualified Database.MySQL.TLS     as TLS+import qualified System.IO.Streams      as Stream+import           Test.Tasty+import           Test.Tasty.HUnit++testConnectInfo :: ConnectInfo+testConnectInfo = defaultConnectInfo {ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell"}++createTable :: MySQLConn -> IO ()+createTable conn = do+    _ <- execute_ conn "DROP TABLE IF EXISTS mariadb_extended"+    _ <- execute_ conn "CREATE TABLE mariadb_extended (n INT, j JSON, u UUID, a INET6, p POINT, s VARCHAR(8))"+    _ <- execute_ conn "INSERT INTO mariadb_extended VALUES (1, '{\"k\":1}', '123e4567-e89b-12d3-a456-426614174000', '::1', POINT(1,2), 'plain')"+    pure ()++columnNamed :: B.ByteString -> [ColumnMetadata] -> IO ColumnMetadata+columnNamed name columns = case filter ((== name) . columnName . columnDefinition) columns of+    [column] -> pure column+    found    -> assertFailure ("expected one column " ++ show name ++ ", got " ++ show found)++selectAll :: Query+selectAll = "SELECT n, j, u, a, s FROM mariadb_extended WHERE n = ?"++expectedRow :: [MySQLValue]+expectedRow =+    [ MySQLInt32 1, MySQLText "{\"k\":1}", MySQLText "123e4567-e89b-12d3-a456-426614174000"+    , MySQLText "::1", MySQLText "plain" ]++tests :: Maybe FilePath -> Maybe FilePath -> TestTree+tests socketPath caPath = testGroup "MariaDB extended column metadata" $+    [ testCase "prepared statement columns name their SQL types" $ do+        conn <- connectMariaDB [MariaDBExtendedMetadata] testConnectInfo+        createTable (mysqlConnection conn)+        (_, params, columns) <- prepareStmtMetadata conn "SELECT n, j, u, a, p, s FROM mariadb_extended WHERE n = ?"+        length params @?= 1+        map (columnName . columnDefinition) columns @?= ["n", "j", "u", "a", "p", "s"]+        n <- columnNamed "n" columns+        columnExtended n @?= []+        j <- columnNamed "j" columns+        columnExtended j @?= [ExtendedFormat "json"]+        u <- columnNamed "u" columns+        columnExtended u @?= [ExtendedTypeName "uuid"]+        a <- columnNamed "a" columns+        columnExtended a @?= [ExtendedTypeName "inet6"]+        p <- columnNamed "p" columns+        columnExtended p @?= [ExtendedTypeName "point"]+        s <- columnNamed "s" columns+        columnExtended s @?= []+        close (mysqlConnection conn)+    , testCase "text and binary queries read rows despite the metadata" $ do+        conn <- connectMariaDB [MariaDBExtendedMetadata] testConnectInfo+        let mysqlConn = mysqlConnection conn+        createTable mysqlConn+        (textColumns, textRows) <- query mysqlConn selectAll [MySQLInt32 1]+        map columnName textColumns @?= ["n", "j", "u", "a", "s"]+        textValues <- Stream.toList textRows+        textValues @?= [expectedRow]+        statement <- prepareStmt mysqlConn selectAll+        (binaryColumns, binaryRows) <- queryStmt mysqlConn statement [MySQLInt32 1]+        map columnType binaryColumns @?= map columnType textColumns+        binaryValues <- Stream.toList binaryRows+        binaryValues @?= [expectedRow]+        close mysqlConn+    ]+    ++ maybe [] (pure . unixSocketTest) socketPath+    ++ maybe [] (pure . tlsTest) caPath++-- | A temporary table keeps tests on separate connections apart.+jsonColumnMetadata :: MariaDBConn -> Assertion+jsonColumnMetadata conn = do+    _ <- execute_ (mysqlConnection conn) "CREATE TEMPORARY TABLE json_only (j JSON)"+    (_, _, columns) <- prepareStmtMetadata conn "SELECT j FROM json_only"+    map columnExtended columns @?= [[ExtendedFormat "json"]]++unixSocketTest :: FilePath -> TestTree+unixSocketTest path = testCase "negotiated over a Unix socket" $ do+    conn <- connectMariaDBUnixSocket [MariaDBExtendedMetadata] path testConnectInfo+    mariadbCapabilities conn @?= [MariaDBExtendedMetadata]+    jsonColumnMetadata conn+    close (mysqlConnection conn)++tlsTest :: FilePath -> TestTree+tlsTest caPath = testCase "negotiated over TLS, where the SSL request carries it" $ do+    cparams <- TLS.makeClientParams (TLS.CustomCAStore caPath)+    conn <- connectMariaDBTLS [MariaDBExtendedMetadata] testConnectInfo (cparams, "Winter")+    mariadbCapabilities conn @?= [MariaDBExtendedMetadata]+    (_, cipher) <- query_ (mysqlConnection conn) "SHOW STATUS LIKE 'Ssl_cipher'"+    cipherRows <- Stream.toList cipher+    assertBool "the connection is encrypted" (cipherRows /= [[MySQLText "Ssl_cipher", MySQLText ""]])+    jsonColumnMetadata conn+    close (mysqlConnection conn)
+ test/MariaDBNegotiation.hs view
@@ -0,0 +1,92 @@+module MariaDBNegotiation (tests) where++import           Control.Concurrent     (threadDelay)+import           Control.Exception      (try)+import qualified Data.ByteString        as B+import           Data.List.NonEmpty     (NonEmpty ((:|)))+import           Database.MariaDB+import           Database.MySQL.Base+import qualified System.IO.Streams      as Stream+import           Test.Tasty+import           Test.Tasty.HUnit++testConnectInfo :: ConnectInfo+testConnectInfo = defaultConnectInfo {ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell"}++-- | Runs on both servers, given the greeting of a plain connection: a MariaDB+-- server negotiates what it offers, and a MySQL server is refused.+tests :: Greeting -> TestTree+tests greeting = testGroup "MariaDB capability negotiation" $+    if offersExtendedCapabilities greeting then mariadbTests else mysqlTests++mariadbTests :: [TestTree]+mariadbTests =+    [ testCase "extended metadata is negotiated, and the MySQL functions work on it" $ do+        conn <- connectMariaDB [MariaDBExtendedMetadata] testConnectInfo+        mariadbCapabilities conn @?= [MariaDBExtendedMetadata]+        (_, rows) <- query_ (mysqlConnection conn) "SELECT 'probe'"+        values <- Stream.toList rows+        values @?= [[MySQLText "probe"]]+        close (mysqlConnection conn)+    , testCase "nothing asked, nothing negotiated" $ do+        conn <- connectMariaDB [] testConnectInfo+        mariadbCapabilities conn @?= []+        result <- try (prepareStmtMetadata conn "SELECT 1")+        case result of+            Left (CapabilityNotNegotiated MariaDBExtendedMetadata) -> pure ()+            Left (CapabilityNotNegotiated MariaDBBulkOperations) ->+                assertFailure "asked for the bulk capability instead of extended metadata"+            Right _ -> assertFailure "prepareStmtMetadata ran without extended metadata"+        close (mysqlConnection conn)+    , testCase "bulk execution with the capability" $ do+        conn <- connectMariaDB [MariaDBBulkOperations] testConnectInfo+        let mysqlConn = mysqlConnection conn+        _ <- execute_ mysqlConn "CREATE TEMPORARY TABLE bulk_negotiated (n INT)"+        statement <- prepareStmt mysqlConn "INSERT INTO bulk_negotiated VALUES (?)"+        ok <- executeBulk conn statement ([BulkValue (MySQLInt32 1)] :| [[BulkValue (MySQLInt32 2)]])+        okAffectedRows ok @?= 2+        close mysqlConn+    ]++mysqlTests :: [TestTree]+mysqlTests =+    [ testCase "connectMariaDB refuses a MySQL server and closes the connection" $ do+        admin <- connect testConnectInfo+        _ <- execute_ admin "CREATE USER IF NOT EXISTS 'mariadbRefusal'@'localhost' IDENTIFIED WITH mysql_native_password BY 'refusal'"+        observer <- connect refusalConnectInfo+        before <- refusalConnections observer+        result <- try (connectMariaDB [MariaDBExtendedMetadata] refusalConnectInfo)+        case result of+            Left (NotMariaDB version) ->+                assertBool ("refused with the server's version, got " ++ show version)+                    ("8." `B.isPrefixOf` version)+            Right conn -> do+                close (mysqlConnection conn)+                assertFailure "connected to a MySQL server"+        after <- settledRefusalConnections observer before (20 :: Int)+        after @?= before+        close observer+        _ <- execute_ admin "DROP USER 'mariadbRefusal'@'localhost'"+        close admin+    ]++-- | A user only this test connects as, so concurrent tests' connections do+-- not show up in its count.+refusalConnectInfo :: ConnectInfo+refusalConnectInfo = defaultConnectInfo {ciUser = "mariadbRefusal", ciPassword = "refusal", ciDatabase = ""}++-- | How many connections the server has open for 'refusalConnectInfo''s user.+-- A user sees its own threads in PROCESSLIST without the PROCESS privilege.+refusalConnections :: MySQLConn -> IO [[MySQLValue]]+refusalConnections conn = do+    (_, rows) <- query_ conn "SELECT COUNT(*) FROM information_schema.PROCESSLIST WHERE USER = 'mariadbRefusal'"+    Stream.toList rows++-- | The server ends a connection's thread shortly after COM_QUIT, so the count+-- may take a moment to drop back; poll for up to a second.+settledRefusalConnections :: MySQLConn -> [[MySQLValue]] -> Int -> IO [[MySQLValue]]+settledRefusalConnections conn expected attempts = do+    current <- refusalConnections conn+    if current == expected || attempts <= 0+        then pure current+        else threadDelay 50000 >> settledRefusalConnections conn expected (attempts - 1)
+ test/MultipleResults.hs view
@@ -0,0 +1,162 @@+-- | Statements whose reply holds several results: multi-statement strings and+-- CALLs. Whatever a function cannot hand back must still be read off the+-- connection, or the next query receives the previous one's leftovers.+module MultipleResults (tests, rowTests) where++import           Control.Exception (try)+import           Data.Int          (Int64)+import           Database.MySQL.Base+import           QueryApi+import qualified System.IO.Streams as Stream+import           System.Timeout    (timeout)+import           Test.Tasty+import           Test.Tasty.HUnit++tests :: TestTree+tests = testGroup "statements with several results"+    [ testCase "execute_ with two INSERTs" $ withTable $ \c -> do+        _ <- execute_ c+            "INSERT INTO several_results VALUES (1); INSERT INTO several_results VALUES (2)"+        assertRowCount c 2+    , testCase "execute_ with a SELECT" $ withTable $ \c ->+        assertExtraResultSets c (execute_ c "SELECT COUNT(*) FROM several_results")+    , testCase "executeStmt with a SELECT" $ withTable $ \c -> do+        stmt <- prepareStmt c "SELECT COUNT(*) FROM several_results"+        assertExtraResultSets c (executeStmt c stmt [])+    , testCase "execute_ with a CALL returning one result set" $ withTable $ \c -> do+        _ <- execute_ c "DROP PROCEDURE IF EXISTS one_result_set"+        _ <- execute_ c "CREATE PROCEDURE one_result_set() SELECT COUNT(*) FROM several_results"+        assertExtraResultSets c (execute_ c "CALL one_result_set()")+    , testCase "execute_ with a failing statement after a SELECT" $ withTable $ \c ->+        assertERRException c+            (execute_ c "SELECT COUNT(*) FROM several_results; SELECT * FROM no_such_table")+    , testCase "queryMulti_ with an INSERT and two SELECTs" $ withTable $ \c -> do+        results <- queryMulti_ c+            "INSERT INTO several_results VALUES (1); SELECT COUNT(*) FROM several_results; \+            \SELECT __id FROM several_results"+        assertEqual "every result, in order"+            [Left 1, Right [[MySQLInt64 1]], Right [[MySQLInt32 1]]]+            (map resultShape results)+        assertRowCount c 1+    , testCase "queryMulti_ with a CALL returning two result sets" $ withTable $ \c -> do+        _ <- execute_ c "DROP PROCEDURE IF EXISTS two_result_sets"+        _ <- execute_ c+            "CREATE PROCEDURE two_result_sets() BEGIN \+            \SELECT COUNT(*) FROM several_results; SELECT COUNT(*) + 1 FROM several_results; END"+        results <- queryMulti_ c "CALL two_result_sets()"+        assertEqual "both result sets, then the CALL's OK"+            [Right [[MySQLInt64 0]], Right [[MySQLInt64 1]], Left 0]+            (map resultShape results)+        assertRowCount c 0+    , testCase "queryMulti with a parameter" $ withTable $ \c -> do+        results <- queryMulti c+            "INSERT INTO several_results VALUES (?); SELECT __id FROM several_results"+            [MySQLInt32 7]+        assertEqual "every result, in order"+            [Left 1, Right [[MySQLInt32 7]]]+            (map resultShape results)+    , testCase "queryMulti_ with a failing statement after a SELECT" $ withTable $ \c ->+        assertERRException c+            (queryMulti_ c "SELECT COUNT(*) FROM several_results; SELECT * FROM no_such_table")+    , testCase "executeMany_ with a SELECT after a DO" $ withTable $ \c ->+        assertExtraResultSets c (executeMany_ c "DO 1; SELECT COUNT(*) FROM several_results")+    , testCase "executeMany with a SELECT" $ withTable $ \c ->+        assertExtraResultSets c+            (executeMany c "SELECT COUNT(*) + ? FROM several_results" [[MySQLInt32 1]])+    , testCase "executeMany with two statements per parameter set" $ withTable $ \c -> do+        oks <- executeMany c+            "INSERT INTO several_results VALUES (?); INSERT INTO several_results VALUES (?)"+            [[MySQLInt32 1, MySQLInt32 2], [MySQLInt32 3, MySQLInt32 4]]+        assertEqual "one OK per statement" [1, 1, 1, 1] (map okAffectedRows oks)+        assertRowCount c 4+    , testCase "executeMany with no parameter sets" $ withTable $ \c -> do+        oks <- executeMany c "INSERT INTO several_results VALUES (?)" ([] :: [[MySQLValue]])+        assertEqual "no OKs" [] (map okAffectedRows oks)+        assertRowCount c 0+    ]++-- | The cases that read rows, run for each API in "QueryApi".+rowTests :: QueryApi -> TestTree+rowTests api = testGroup "statements with several results, read as rows"+    [ testCase "query_ with two INSERTs" $ withTable $ \c -> do+        (_, rows) <- apiQuery_ api (ColumnCount 0) c+            "INSERT INTO several_results VALUES (1); INSERT INTO several_results VALUES (2)"+        Stream.skipToEof rows+        assertRowCount c 2+    , testCase "query_ with a CALL returning one result set" $ withTable $ \c -> do+        _ <- execute_ c "DROP PROCEDURE IF EXISTS one_result_set"+        _ <- execute_ c "CREATE PROCEDURE one_result_set() SELECT COUNT(*) FROM several_results"+        (_, rows) <- apiQuery_ api (ColumnCount 1) c "CALL one_result_set()"+        counts <- Stream.toList rows+        assertEqual "rows of the procedure's SELECT" [[MySQLInt64 0]] counts+        assertRowCount c 0+    , testCase "query_ with an INSERT before a SELECT" $ withTable $ \c -> do+        (_, rows) <- apiQuery_ api (ColumnCount 1) c+            "INSERT INTO several_results VALUES (1); SELECT COUNT(*) FROM several_results"+        counts <- Stream.toList rows+        assertEqual "rows of the SELECT" [[MySQLInt64 1]] counts+        assertRowCount c 1+    , testCase "query_ with two SELECTs" $ withTable $ \c -> do+        (_, rows) <- apiQuery_ api (ColumnCount 1) c+            "SELECT COUNT(*) FROM several_results; SELECT COUNT(*) FROM several_results"+        outcome <- try (Stream.toList rows)+        case outcome of+            Left ExtraResultSets -> pure ()+            Right _ -> assertFailure "the second SELECT's result set went unreported"+        again <- Stream.read rows+        assertEqual "reading the finished rows again" Nothing again+        assertRowCount c 0+    , testCase "query_ with a failing statement after a SELECT" $ withTable $ \c -> do+        (_, rows) <- apiQuery_ api (ColumnCount 1) c+            "SELECT COUNT(*) FROM several_results; SELECT * FROM no_such_table"+        assertERRException c (Stream.toList rows)+    ]++-- | The affected rows of an 'OK', or the rows of a result set.+resultShape :: StatementResult -> Either Int [[MySQLValue]]+resultShape result = case result of+    StatementOK ok -> Left (okAffectedRows ok)+    StatementRows _ rows -> Right rows++-- | Generous for a few statements on an empty temporary table, and short enough+-- that a hang fails the suite instead of stalling CI.+replyTimeLimitMicroseconds :: Int+replyTimeLimitMicroseconds = 10000000++-- | Runs a case on a fresh connection with an empty temporary table.+withTable :: (MySQLConn -> Assertion) -> Assertion+withTable body = do+    (_, c) <- connectDetail defaultConnectInfo+        { ciUser = "testMySQLHaskell"+        , ciDatabase = "testMySQLHaskell"+        }+    _ <- execute_ c "CREATE TEMPORARY TABLE several_results (__id INT)"+    finished <- timeout replyTimeLimitMicroseconds (body c)+    case finished of+        Nothing -> assertFailure "blocked for 10 s waiting for a reply"+        Just () -> close c++-- | The statement must raise 'ExtraResultSets' and leave the connection in step.+assertExtraResultSets :: MySQLConn -> IO a -> Assertion+assertExtraResultSets c runStatement = do+    outcome <- try runStatement+    case outcome of+        Left ExtraResultSets -> pure ()+        Right _ -> assertFailure "the result set went unreported"+    assertRowCount c 0++-- | The failing statement's error must come from this call, not the next one.+assertERRException :: MySQLConn -> IO a -> Assertion+assertERRException c runStatement = do+    outcome <- try runStatement+    case outcome of+        Left (ERRException _) -> pure ()+        Right _ -> assertFailure "the failing statement's error went unreported"+    assertRowCount c 0++-- | The connection must be back in step: a fresh query gets its own rows.+assertRowCount :: MySQLConn -> Int64 -> Assertion+assertRowCount c expected = do+    (_, rows) <- query_ c "SELECT COUNT(*) FROM several_results"+    counts <- Stream.toList rows+    assertEqual "rows of the next query" [[MySQLInt64 expected]] counts
+ test/MysqlTests.hs view
@@ -0,0 +1,190 @@+{-# LANGUAGE ScopedTypeVariables #-}++module MysqlTests(tests)  where++import qualified BinaryRow+import qualified BinaryRowNew+import qualified BinLog+import qualified BinLogNew+import           Control.Concurrent    (forkIO, threadDelay)+import           Control.Exception     (bracket, catch)+import           Control.Monad+import qualified Data.ByteString       as B+import           Database.MySQL.Base+import           Database.MySQL.BinLog+import           System.Environment+import qualified System.IO.Streams as Stream+import           Test.Tasty+import           Test.Tasty.HUnit+import qualified TextRow+import qualified ExecuteMany+import           QueryApi+import qualified TextRowNew++tests :: TestTree+tests = testCaseSteps "mysql-haskell test suit" $ \step -> do++    step "preparing table..."+    (greet, c) <- connectDetail defaultConnectInfo {ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell"}++    let ver = greetingVersion greet+        isOld = "5.0" `B.isPrefixOf` ver+                || "5.1" `B.isPrefixOf` ver+                || "5.5" `B.isPrefixOf` ver+        isNew = not isOld  -- MySQL 5.6+ and MariaDB 10+ support fractional seconds+                           -- in TIME, DATETIME, and TIMESTAMP columns+++    execute_ c "DROP TABLE IF EXISTS test"+    execute_ c "DROP TABLE IF EXISTS test_new"++    execute_ c  "CREATE TABLE test(\+                \__id           INT,\+                \__bit          BIT(16),\+                \__tinyInt      TINYINT,\+                \__tinyIntU     TINYINT UNSIGNED,\+                \__smallInt     SMALLINT,\+                \__smallIntU    SMALLINT UNSIGNED,\+                \__mediumInt    MEDIUMINT,\+                \__mediumIntU   MEDIUMINT UNSIGNED,\+                \__int          INT,\+                \__intU         INT UNSIGNED,\+                \__bigInt       BIGINT,\+                \__bigIntU      BIGINT UNSIGNED,\+                \__decimal      DECIMAL(20,10),\+                \__float        FLOAT,\+                \__double       DOUBLE,\+                \__date         DATE,\+                \__datetime     DATETIME,\+                \__timestamp    TIMESTAMP NULL,\+                \__time         TIME,\+                \__year         YEAR(4),\+                \__char         CHAR(8),\+                \__varchar      VARCHAR(1024),\+                \__binary       BINARY(8),\+                \__varbinary    VARBINARY(1024),\+                \__tinyblob     TINYBLOB,\+                \__tinytext     TINYTEXT,\+                \__blob         BLOB(1000000),\+                \__text         TEXT(1000000),\+                \__enum         ENUM('foo', 'bar', 'qux'),\+                \__set          SET('foo', 'bar', 'qux')\+                \) CHARACTER SET utf8"++    resetTestTable c++    step "testing executeMany"+    ExecuteMany.tests c++    resetTestTable c++    forM_ queryApis $ \api -> do+        step ("testing text protocol through the " ++ apiName api)+        TextRow.tests api c++        resetTestTable c++        step ("testing binary protocol through the " ++ apiName api)+        BinaryRow.tests api c++        resetTestTable c+++    when isNew $ do+        execute_ c "CREATE TABLE test_new(\+                   \__id           INT,\+                   \__datetime     DATETIME(2),\+                   \__timestamp    TIMESTAMP(4) NULL,\+                   \__time         TIME(6)\+                   \) CHARACTER SET utf8"++        forM_ queryApis $ \api -> do+            resetTest57Table c++            step ("testing MySQL5.7 extra text protocol through the " ++ apiName api)+            TextRowNew.tests api c++            resetTest57Table c++            step ("testing MySQL5.7 extra binary protocol through the " ++ apiName api)+            BinaryRowNew.tests api c++        void $ resetTest57Table c++    step "testing binlog protocol"++    if isNew+    then do+        forkIO BinLogNew.eventProducer+        BinLogNew.tests c+    else do+        forkIO BinLog.eventProducer+        BinLog.tests c++    close c++    step "testing password change"+    (_, c) <- connectDetail defaultConnectInfo {ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell"}+    -- ALTER USER works on both MariaDB and MySQL 8.0 (SET PASSWORD = PASSWORD('...') was removed in MySQL 8.0)+    execute_ c "ALTER USER 'testMySQLHaskell'@'localhost' IDENTIFIED BY '123456abcdefg???'"+    close c++    (_, c) <- connectDetail+        defaultConnectInfo {ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell", ciPassword = "123456abcdefg???"}+    execute_ c "ALTER USER 'testMySQLHaskell'@'localhost' IDENTIFIED BY ''"+    close c++    catch+        (void $ connectDetail+                    defaultConnectInfo+                        {ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell", ciPassword = "wrongPassWord"})+        (\ (e :: ERRException) -> do+            let ERRException err = e+            assertEqual "wrong password should fail with error 1045" 1045 (errCode err))++  where+    resetTestTable c = do+            execute_ c  "DELETE FROM test WHERE __id=0"+            execute_ c  "INSERT INTO test VALUES(\+                    \0,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL,\+                    \NULL\+                    \)"++    resetTest57Table c = do+            execute_ c  "DELETE FROM test_new WHERE __id=0"+            execute_ c  "INSERT INTO test_new VALUES(\+                        \0,\+                        \NULL,\+                        \NULL,\+                        \NULL\+                        \)"++
+ test/NonUtf8Results.hs view
@@ -0,0 +1,47 @@+-- | Results in a charset other than UTF-8 or latin1, here gbk after+-- @SET NAMES gbk@. 'query_' hands the rows over and a string whose bytes are+-- not UTF-8 raises 'InvalidUtf8Text' when used; the typed decoders refuse the+-- row while reading it. 1.4.0 refused the row in 'query_' too.+module NonUtf8Results (tests) where++import           Control.Exception      (evaluate, try)+import           Control.Monad          (void)+import           Data.Word              (Word16)+import           Database.MySQL.Base+import qualified Database.MySQL.Decoder as Decode+import qualified System.IO.Streams      as Stream+import           Test.Tasty+import           Test.Tasty.HUnit+import           TestUser               (withTestConnection)++tests :: TestTree+tests = testGroup "results in gbk"+    [ testCase "query_ reads the row; the string that is not UTF-8 raises when used" $+        withTestConnection $ \c -> do+            void (execute_ c "SET NAMES gbk")+            (_, rows) <- query_ c gbkQuery+            values <- Stream.toList rows+            case values of+                [[ok, nihao]] -> do+                    ok @?= MySQLText "ok"+                    used <- try (evaluate nihao)+                    used @?= Left (InvalidUtf8Text gbkChineseCi "\xC4\xE3")+                other -> assertFailure ("expected one row of two values, got " ++ show (length other))+    , testCase "queryRows_ with Decode.text refuses the row while reading it" $+        withTestConnection $ \c -> do+            void (execute_ c "SET NAMES gbk")+            (_, rows) <- queryRows_ ((,) <$> Decode.field Decode.text <*> Decode.field Decode.text) c gbkQuery+            refused <- try (Stream.toList rows)+            case refused of+                Left (Decode.FieldError (ColumnNumber 1) (Decode.FieldInvalidUtf8 bytes)) -> bytes @?= "\xC4\xE3"+                Left other -> assertFailure ("refused with " ++ show other)+                Right decoded -> assertFailure ("decoded " ++ show decoded)+    ]++-- | 你, which gbk sends as the two bytes C4 E3.+gbkQuery :: Query+gbkQuery = "SELECT 'ok', CONVERT(_utf8mb4 X'E4BDA0' USING gbk)"++-- | The id the server gives gbk results' default collation.+gbkChineseCi :: Word16+gbkChineseCi = 28
+ test/Orphans.hs view
@@ -0,0 +1,25 @@+{-# OPTIONS_GHC -Wno-orphans #-}++-- https://tech.freckle.com/2018/12/12/a-home-for-orphan-instances/+module Orphans where++import Test.QuickCheck++import Data.Int.Int24+import Data.Word.Word24++-- Arbitrary/CoArbitrary instances for Int24 and Word24++instance Arbitrary Int24 where+  arbitrary = arbitraryBoundedIntegral+  shrink = shrinkIntegral++instance CoArbitrary Int24 where+  coarbitrary = coarbitraryIntegral++instance Arbitrary Word24 where+  arbitrary = arbitraryBoundedIntegral+  shrink = shrinkIntegral++instance CoArbitrary Word24 where+  coarbitrary = coarbitraryIntegral
+ test/PacketClassification.hs view
@@ -0,0 +1,36 @@+-- | Telling replies apart by their first byte. A row can start with the EOF+-- marker, and an empty packet has no first byte at all.+module PacketClassification (tests) where++import           Control.Exception              (SomeException, evaluate, try)+import qualified Data.ByteString.Lazy           as L+import           Database.MySQL.Protocol.Packet+import           Test.Tasty+import           Test.Tasty.HUnit++tests :: TestTree+tests = testGroup "packet classification"+    [ testCase "a row whose first value is 16 MiB or longer is not EOF" $ do+        -- 0xFE then the 8-byte length 16777216, then the value's first bytes.+        assertBool "row taken for EOF" (not (isEOF (packet ("\xFE\x00\x00\x00\x01\x00\x00\x00\x00" <> "abc"))))+        assertBool "EOF packet not recognised" (isEOF (packet "\xFE\x00\x00\x02\x00"))+    , testCase "an empty packet is neither OK, ERR nor EOF" $ do+        classified <- try (evaluate (isOK empty || isERR empty || isEOF empty))+        case classified of+            Left (failure :: SomeException) -> assertFailure ("classifying crashed: " <> show failure)+            Right anyMatch -> assertBool "empty packet classified" (not anyMatch)+    , testCase "an OK packet keeps an insert ID above Int's range" $ do+        ok <- decodeFromPacket (packet ("\x00\x01\xFE" <> "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" <> "\x02\x00\x00\x00")) :: IO OK+        assertEqual "last insert ID" (18446744073709551615 :: Integer) (toInteger (okLastInsertID ok))+    , testCase "an OK packet with affected rows above Int's range fails to decode" $ do+        decoded <- try (decodeFromPacket (packet ("\x00\xFE" <> "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" <> "\x00\x02\x00\x00\x00")) :: IO OK)+        case decoded of+            Left (DecodePacketFailed _ _ _) -> pure ()+            Right ok -> assertFailure ("decoded as " <> show ok)+    ]++packet :: L.ByteString -> Packet+packet body = Packet (L.length body) 1 body++empty :: Packet+empty = packet ""
+ test/ParameterEscaping.hs view
@@ -0,0 +1,99 @@+-- | Values that 'query', 'execute' and 'executeMany' render into the query+-- text, read back under the default sql_mode and under NO_BACKSLASH_ESCAPES.+-- In that mode a backslash is an ordinary character, so a quote escaped as+-- backslash-quote used to end the string literal and let the rest of the+-- value run as SQL.+module ParameterEscaping (tests) where++import qualified Data.ByteString     as B+import           Data.Text           (Text)+import qualified Data.Text           as T+import           Data.Word           (Word8)+import           Database.MySQL.Base+import           QueryApi+import qualified System.IO.Streams   as Stream+import           Test.Tasty+import           Test.Tasty.HUnit+import           TestUser            (withTestConnection)++tests :: QueryApi -> TestTree+tests api = testGroup "parameters rendered into the query text"+    [ testGroup "NO_BACKSLASH_ESCAPES"+        [ testCase "a quote in a query parameter does not end the literal" $+            withUsers $ \c -> do+                (_, rows) <- apiQuery api (ColumnCount 2) c+                    "SELECT name, secret FROM escaping_users WHERE name = ?"+                    [One (MySQLText endsLiteral)]+                found <- Stream.toList rows+                assertEqual "rows matching the name" [] found+        , testCase "a quote in an executeMany parameter does not end the literal" $+            withUsers $ \c -> do+                oks <- executeMany c+                    "UPDATE escaping_users SET secret = 'changed' WHERE name = ?"+                    [[MySQLText endsLiteral]]+                assertEqual "rows changed" 0 (sum (map okAffectedRows oks))+        , testCase "text with quotes and control characters round-trips" $+            withTestConnection $ \c -> do+                setNoBackslashEscapes c+                assertTextRoundTrip api c (T.filter (/= '\\') trickyText)+        , testCase "every byte but the backslash round-trips" $+            withTestConnection $ \c -> do+                setNoBackslashEscapes c+                assertBytesRoundTrip api c (B.filter (/= backslash) everyByte)+        ]+    , testGroup "default sql_mode"+        [ testCase "text with quotes, backslashes and control characters round-trips" $+            withTestConnection $ \c -> assertTextRoundTrip api c trickyText+        , testCase "every byte round-trips" $+            withTestConnection $ \c -> assertBytesRoundTrip api c everyByte+        ]+    ]++-- | A name that matches every row once its quote ends the string literal.+endsLiteral :: Text+endsLiteral = "nobody' OR 1=1 #"++-- | Ends in a backslash, which in the default mode escapes the closing quote+-- unless it is escaped itself.+trickyText :: Text+trickyText = "it's \"quoted\" '' \\' \\\\ \0 \n \r \t \b \SUB % _ \252nic\246de \128512 \\"++everyByte :: B.ByteString+everyByte = B.pack [minBound .. maxBound]++backslash :: Word8+backslash = 0x5C++-- | A connection under NO_BACKSLASH_ESCAPES with one row in @escaping_users@+-- whose name is not 'endsLiteral'.+withUsers :: (MySQLConn -> IO a) -> IO a+withUsers run = withTestConnection $ \c -> do+    _ <- execute_ c "CREATE TEMPORARY TABLE escaping_users (name VARCHAR(64), secret VARCHAR(64))"+    _ <- execute_ c "INSERT INTO escaping_users VALUES ('alice', 'hunter2')"+    setNoBackslashEscapes c+    run c++setNoBackslashEscapes :: MySQLConn -> IO ()+setNoBackslashEscapes c = do+    _ <- execute_ c "SET SESSION sql_mode = 'NO_BACKSLASH_ESCAPES'"+    pure ()++-- | Inserts with 'execute' and selects with the value in the WHERE clause, so+-- both the stored value and the literal compared against it must be exact.+assertTextRoundTrip :: QueryApi -> MySQLConn -> Text -> Assertion+assertTextRoundTrip api c text = do+    _ <- execute_ c "CREATE TEMPORARY TABLE escaping_text (value VARCHAR(255) CHARACTER SET utf8mb4)"+    _ <- execute c "INSERT INTO escaping_text VALUES (?)" [MySQLText text]+    (_, rows) <- apiQuery api (ColumnCount 1) c "SELECT value FROM escaping_text WHERE value = ?"+        [One (MySQLText text)]+    found <- Stream.toList rows+    assertEqual "rows read back" [[MySQLText text]] found++assertBytesRoundTrip :: QueryApi -> MySQLConn -> B.ByteString -> Assertion+assertBytesRoundTrip api c bytes = do+    _ <- execute_ c "CREATE TEMPORARY TABLE escaping_bytes (value VARBINARY(512))"+    _ <- execute c "INSERT INTO escaping_bytes VALUES (?)" [MySQLBytes bytes]+    (_, rows) <- apiQuery api (ColumnCount 1) c "SELECT value FROM escaping_bytes WHERE value = ?"+        [One (MySQLBytes bytes)]+    found <- Stream.toList rows+    assertEqual "rows read back" [[MySQLBytes bytes]] found
+ test/PlaceholderLiterals.hs view
@@ -0,0 +1,18 @@+-- | A question mark inside a string literal or a comment sent to the server+-- through 'query', where it used to count as a placeholder.+module PlaceholderLiterals (tests) where++import           Database.MySQL.Base+import           QueryApi+import qualified System.IO.Streams   as Stream+import           Test.Tasty+import           Test.Tasty.HUnit+import           TestUser            (withTestConnection)++tests :: QueryApi -> TestTree+tests api = testCase "a question mark in a literal or comment is not a placeholder" $+    withTestConnection $ \c -> do+        (_, rows) <- apiQuery api (ColumnCount 2) c "SELECT ?, '?' -- is this a placeholder?\n"+            [One (MySQLText "filled")]+        result <- Stream.toList rows+        assertEqual "rows" [[MySQLText "filled", MySQLText "?"]] result
+ test/Placeholders.hs view
@@ -0,0 +1,38 @@+-- | Which question marks 'renderParams' fills: not those inside quotes or+-- comments, which used to count as placeholders.+module Placeholders (tests) where++import           Control.Exception              (evaluate, try)+import qualified Data.ByteString.Lazy           as L+import           Database.MySQL.Base+import           Test.Tasty+import           Test.Tasty.HUnit++tests :: TestTree+tests = testGroup "placeholders"+    [ renders "single-quoted string" "SELECT '?', ?" [one] "SELECT '?', 1"+    , renders "double-quoted string and quoted identifier"+        "SELECT \"?\", `a?b`, ?" [one] "SELECT \"?\", `a?b`, 1"+    , renders "doubled and escaped quotes stay inside the string"+        "SELECT 'it''s ?', 'C\\'s ?', ?" [one] "SELECT 'it''s ?', 'C\\'s ?', 1"+    , renders "a backslash does not escape in a quoted identifier"+        "SELECT `a\\`, ?, '?'" [one] "SELECT `a\\`, 1, '?'"+    , renders "dash comment" "SELECT ? -- why?\n, ?" [one, two] "SELECT 1 -- why?\n, 2"+    , renders "hash comment" "SELECT ? # why?\n, ?" [one, two] "SELECT 1 # why?\n, 2"+    , renders "block comment" "SELECT /* ? */ ?" [one] "SELECT /* ? */ 1"+    , renders "two dashes without a space are arithmetic" "SELECT ?--?, '?'" [one, two] "SELECT 1--2, '?'"+    , renders "an executable comment is SQL" "SELECT /*! ? */ '?'" [one] "SELECT /*! 1 */ '?'"+    , testCase "more parameters than placeholders outside quotes" $ do+        rendered <- try (evaluate (L.length (fromQuery (renderParams "SELECT ?, '?'" [one, two]))))+        case rendered of+            Left WrongParamsCount -> pure ()+            Right _ -> assertFailure "rendered the second parameter into the string literal"+    ]++renders :: TestName -> Query -> [MySQLValue] -> L.ByteString -> TestTree+renders name sql params expected = testCase name $+    assertEqual "rendered SQL" expected (fromQuery (renderParams sql params))++one, two :: MySQLValue+one = MySQLInt32 1+two = MySQLInt32 2
+ test/QC/ByteString.hs view
@@ -0,0 +1,175 @@+{-# LANGUAGE BangPatterns, CPP, OverloadedStrings #-}+module QC.ByteString (tests) where++import Data.Char (chr, ord)+import Data.Int (Int64)+import Data.Word (Word8)+import Prelude hiding (take, takeWhile)+import QC.Common (liftOp, parseBS, toStrictBS)+import Test.Tasty (TestTree)+import Test.Tasty.QuickCheck (testProperty)+import Test.QuickCheck+import qualified Data.Scientific as Sci+import qualified Data.ByteString.Builder.Scientific as Sci+import qualified Data.Binary.Parser as P+import qualified Data.ByteString as B+import qualified Data.ByteString.Builder as BB+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Lazy.Char8 as L8++newtype ASCIIChar = ASCIIChar Char+    deriving (Eq, Ord, Show, Read)+instance Arbitrary ASCIIChar where+    arbitrary = ASCIIChar <$> choose ('\0', '\127')+    shrink (ASCIIChar c) = ASCIIChar <$> shrink c++-- Basic byte-level combinators.++satisfy :: Word8 -> L.ByteString -> Property+satisfy w s = parseBS (P.satisfy (<=w)) (L.cons w s) === Just w++satisfyWith :: ASCIIChar -> L.ByteString -> Property+satisfyWith (ASCIIChar c) s = parseBS (P.satisfyWith (chr . fromIntegral) (<=c))+                         (L.cons (fromIntegral (ord c)) s) === Just c++word8 :: Word8 -> L.ByteString -> Property+word8 w s = parseBS (P.word8 w *> pure w) (L.cons w s) === Just w++skipWord8 :: Word8 -> L.ByteString -> Property+skipWord8 w s =+  case (parseBS (P.skipWord8 (<w)) s, L.uncons s) of+    (Nothing, mcs) -> maybe (property True) (expectFailure . it) mcs+    (Just _,  mcs) -> maybe (property False) it mcs+  where it cs = liftOp "<" (<) (fst cs) w++anyWord8 :: L.ByteString -> Property+anyWord8 s+    | L.null s  = p === Nothing+    | otherwise = p === Just (L.head s)+  where p = parseBS P.anyWord8 s++peekMaybe :: L.ByteString -> Property+peekMaybe s+    | L.null s  = p === Just (Nothing, s)+    | otherwise = p === Just (Just (L.head s), s)+  where p = parseBS ((,) <$> P.peekMaybe <*> P.getRemainingLazyByteString) s++peek :: L.ByteString -> Property+peek s = parseBS P.peek s === (fst <$> L.uncons s)++string :: L.ByteString -> L.ByteString -> Property+string s t = parseBS (P.string s' *> pure s') (s `L.append` t) === Just s'+  where s' = toStrictBS s++strings :: L.ByteString -> L.ByteString -> L.ByteString -> Property+strings s t u =+    parseBS (P.string (toStrictBS s) >> (P.string t' *> pure t')) (L.concat [s,t,u])+    === Just t'+  where t' = toStrictBS t++skipWhile :: Word8 -> L.ByteString -> Property+skipWhile w s =+    let t = L.dropWhile (<=w) s+    in case P.runGetOrFail (P.skipWhile (<=w)) s of+         Right (t', _, _) -> t === t'+         Left  _          -> property False++takeCount :: Positive Int -> L.ByteString -> Property+takeCount (Positive k) s =+    case parseBS (P.getByteString k) s of+      Nothing -> liftOp ">" (>) (fromIntegral k) (L.length s)+      Just _s -> liftOp "<=" (<=) (fromIntegral k) (L.length s)++takeWhile :: Word8 -> L.ByteString -> Property+takeWhile w s =+    let (h,t) = L.span (==w) s+    in case P.runGetOrFail (P.takeWhile (==w)) s of+         Right (t', _, h') -> t === t' .&&. toStrictBS h === h'+         Left _            -> property False++take :: Int -> L.ByteString -> Property+take n s = maybe (property $ L.length s < fromIntegral n)+           (=== B.take n (toStrictBS s)) $+           parseBS (P.getByteString n) s++remaining :: L.ByteString -> Property+remaining s = maybe (property False) (=== s) .+                       parseBS P.getRemainingLazyByteString $ s++takeWhile1 :: Word8 -> L.ByteString -> Property+takeWhile1 w s =+    let s'    = L.cons w s+        (h,t) = L.span (<=w) s'+    in case P.runGetOrFail (P.takeWhile1 (<=w)) s' of+         Right (t', _, h') -> t === t' .&&. toStrictBS h === h'+         _                 -> property False++takeTill :: Word8 -> L.ByteString -> Property+takeTill w s =+    let (h,t) = L.break (==w) s+    in case P.runGetOrFail (P.takeTill (==w)) s of+         Right (t', _, h') -> t === t' .&&. toStrictBS h === h'+         _                 -> property False++takeWhile1_empty :: Property+takeWhile1_empty = parseBS (P.takeWhile1 undefined) L.empty === Nothing++endOfInput :: L.ByteString -> Property+endOfInput s = parseBS P.endOfInput s === if L.null s+                                          then Just ()+                                          else Nothing++endOfLine :: L.ByteString -> Property+endOfLine s =+  case (parseBS P.endOfLine s, L8.uncons s) of+    (Nothing, mcs) -> maybe (property True) (expectFailure . eol) mcs+    (Just _,  mcs) -> maybe (property False) eol mcs+  where eol (c,s') = c === '\n' .||.+                     (c, fst <$> L8.uncons s') === ('\r', Just '\n')++scan :: L.ByteString -> Positive Int64 -> Property+scan s (Positive k) = parseBS p s === Just (toStrictBS $ L.take k s)+  where p = P.scan k $ \ n _ ->+            if n > 0 then let !n' = n - 1 in Just n' else Nothing++decimal :: Integer -> Property+decimal d =+    let dBS = BB.toLazyByteString $ BB.integerDec d+    in parseBS (P.signed P.decimal) dBS === Just d++double :: Double -> Property+double d =+    let dBS = BB.toLazyByteString $ BB.doubleDec d+    in parseBS P.double dBS === Just d++scientific :: Sci.Scientific -> Property+scientific sci =+    let sciBS = BB.toLazyByteString $ Sci.formatScientificBuilder Sci.Generic Nothing sci+    in parseBS P.scientific sciBS === Just sci++tests :: [TestTree]+tests = [+      testProperty "anyWord8" anyWord8+    , testProperty "endOfInput" endOfInput+    , testProperty "endOfLine" endOfLine+    , testProperty "peekMaybe" peekMaybe+    , testProperty "peek" peek+    , testProperty "satisfy" satisfy+    , testProperty "satisfyWith" satisfyWith+    , testProperty "scan" scan+    , testProperty "skipWord8" skipWord8+    , testProperty "skipWhile" skipWhile+    , testProperty "string" string+    , testProperty "strings" strings+    , testProperty "take" take+    , testProperty "takeCount" takeCount+    , testProperty "remaining" remaining+    , testProperty "takeTill" takeTill+    , testProperty "takeWhile" takeWhile+    , testProperty "takeWhile1" takeWhile1+    , testProperty "takeWhile1_empty" takeWhile1_empty+    , testProperty "word8" word8+    , testProperty "decimal" decimal+    , testProperty "double" double+    , testProperty "scientific" scientific+  ]
+ test/QC/Combinator.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE CPP, OverloadedStrings #-}++module QC.Combinator where++import qualified Control.Monad as M (replicateM)+import Data.Maybe (fromJust, isJust)+import Data.Word (Word8)+import QC.Common (Repack, parseBS, repackBS, toLazyBS)+import Test.Tasty (TestTree)+import Test.Tasty.QuickCheck (testProperty)+import Test.QuickCheck+import qualified Data.Binary.Parser as P+import qualified Data.ByteString as B+import qualified Data.Foldable as Foldable (asum)++asum :: NonEmptyList (NonEmptyList Word8) -> Gen Property+asum (NonEmpty xs) = do+  let ys = map (B.pack . getNonEmpty) xs+  return . forAll (repackBS <$> arbitrary <*> elements ys) $+    maybe False (`elem` ys) . parseBS (Foldable.asum (map (\s -> P.string s *> pure s) ys))++replicateM :: Positive (Small Int) -> Repack -> B.ByteString -> Bool+replicateM (Positive (Small n)) rs s =+    (length <$> parseBS (M.replicateM n (P.string s)) input) == Just n+  where input = repackBS rs (B.concat (replicate (n+1) s))++lookAhead :: NonEmptyList Word8 -> Bool+lookAhead (NonEmpty xs) =+  let ys = B.pack xs+      withLookAheadThenConsume = (\x y -> (x, y)) <$> P.lookAhead (P.string ys) <*> P.string ys+      mr = parseBS withLookAheadThenConsume $ toLazyBS ys+  in isJust mr && fst (fromJust mr) == snd (fromJust mr)++tests :: [TestTree]+tests = [+    testProperty "asum" asum+  , testProperty "replicateM" replicateM+  , testProperty "lookAhead" lookAhead+  ]
+ test/QC/Common.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE CPP, FlexibleInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module QC.Common+    (+      ASCII(..)+    , parseBS+    , toLazyBS+    , toStrictBS+    , Repack+    , repackBS+    , repackBS_+    , liftOp+    ) where++import Data.Char (isAlpha)+import Test.QuickCheck+import Test.QuickCheck.Instances ()+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL+import qualified Data.Binary.Parser as P++parseBS :: P.Get r -> BL.ByteString -> Maybe r+parseBS p lbs = case P.parseLazy p lbs of+    Left _ -> Nothing+    Right v -> Just v++toStrictBS :: BL.ByteString -> B.ByteString+toStrictBS = B.concat . BL.toChunks++toLazyBS :: B.ByteString -> BL.ByteString+toLazyBS = BL.fromChunks . (:[])++newtype ASCII a = ASCII { fromASCII :: a }+                  deriving (Eq, Ord, Show)++instance Arbitrary (ASCII B.ByteString) where+    arbitrary = (ASCII . B.pack) <$> listOf (choose (0,127))+    shrink = map (ASCII . B.pack) . shrink . B.unpack . fromASCII++instance Arbitrary (ASCII BL.ByteString) where+    arbitrary = ASCII <$> (repackBS <$> arbitrary <*> (fromASCII <$> arbitrary))+    shrink = map (ASCII . BL.pack) . shrink . BL.unpack . fromASCII++type Repack = NonEmptyList (Positive (Small Int))++repackBS :: Repack -> B.ByteString -> BL.ByteString+repackBS (NonEmpty bs) =+    BL.fromChunks . repackBS_ (map (getSmall . getPositive) bs)++repackBS_ :: [Int] -> B.ByteString -> [B.ByteString]+repackBS_ = go . cycle+  where go (b:bs) s+          | B.null s = []+          | otherwise = let (h,t) = B.splitAt b s+                        in h : go bs t+        go _ _ = error "unpossible"++liftOp :: (Show a, Testable prop) =>+          String -> (a -> a -> prop) -> a -> a -> Property+liftOp name f x y = counterexample desc (f x y)+  where op = case name of+               (c:_) | isAlpha c -> " `" ++ name ++ "` "+                     | otherwise -> " " ++ name ++ " "+               _ -> " ??? "+        desc = "not (" ++ show x ++ op ++ show y ++ ")"
+ test/QueryApi.hs view
@@ -0,0 +1,136 @@+-- | The calls the integration tests read rows with. Every suite that reads+-- rows takes a 'QueryApi' and runs once for each of 'queryApis', so the old+-- 'MySQLValue' functions and each path of "Database.MySQL.Decoder" go through+-- the very same cases, and a test differs between them only in the API.+module QueryApi+    ( QueryApi (..)+    , ColumnCount (..)+    , queryApis+    , mysqlValueApis+    ) where++import           Control.Exception                  (throwIO)+import           Control.Monad                      (replicateM)+import qualified Data.Vector                        as V+import           Database.MySQL.Base+import qualified Database.MySQL.Decoder             as Decode+import           System.IO.Streams                  (InputStream)+import           TypedValue                         (typedDecoder)+import qualified System.IO.Streams                  as Stream++-- | How many columns a query returns. Only the 'Decode.RowDecoder' path uses+-- it: a row decoder states its columns before the query runs, where the other+-- paths learn them from the result set.+newtype ColumnCount = ColumnCount Int++data QueryApi = QueryApi+    { apiName            :: String+    , apiQuery_          :: ColumnCount -> MySQLConn -> Query+                         -> IO ([ColumnDef], InputStream [MySQLValue])+    , apiQuery           :: ColumnCount -> MySQLConn -> Query -> [Param]+                         -> IO ([ColumnDef], InputStream [MySQLValue])+    , apiQueryVector_    :: ColumnCount -> MySQLConn -> Query+                         -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))+    , apiQueryStmt       :: ColumnCount -> MySQLConn -> StmtID -> [MySQLValue]+                         -> IO ([ColumnDef], InputStream [MySQLValue])+    , apiQueryStmtVector :: ColumnCount -> MySQLConn -> StmtID -> [MySQLValue]+                         -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))+    }++queryApis :: [QueryApi]+queryApis = mysqlValueApis ++ [typedRawRowApi]++-- | The APIs that give every field as the 'MySQLValue' the old API does, also+-- the dates a 'Data.Time.Calendar.Day' cannot hold.+mysqlValueApis :: [QueryApi]+mysqlValueApis = [valueApi, rowDecoderApi, rawRowApi]++-- | The API the tests were written for.+valueApi :: QueryApi+valueApi = QueryApi+    { apiName            = "MySQLValue API"+    , apiQuery_          = const query_+    , apiQuery           = const query+    , apiQueryVector_    = const queryVector_+    , apiQueryStmt       = const queryStmt+    , apiQueryStmtVector = const queryStmtVector+    }++-- | 'queryRows_', 'queryRows' and 'queryStmtRows' with 'Decode.mysqlValue' for+-- every column.+rowDecoderApi :: QueryApi+rowDecoderApi = QueryApi+    { apiName            = "RowDecoder API"+    , apiQuery_          = queryRows_ . everyColumn+    , apiQuery           = queryRows . everyColumn+    , apiQueryVector_    = rowDecoderQueryVector_+    , apiQueryStmt       = queryStmtRows . everyColumn+    , apiQueryStmtVector = rowDecoderQueryStmtVector+    }++everyColumn :: ColumnCount -> Decode.RowDecoder [MySQLValue]+everyColumn (ColumnCount count) = replicateM count (Decode.field Decode.mysqlValue)++rowDecoderQueryVector_ :: ColumnCount -> MySQLConn -> Query+                       -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))+rowDecoderQueryVector_ count conn qry = toVectors =<< queryRows_ (everyColumn count) conn qry++rowDecoderQueryStmtVector :: ColumnCount -> MySQLConn -> StmtID -> [MySQLValue]+                          -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))+rowDecoderQueryStmtVector count conn stmt params =+    toVectors =<< queryStmtRows (everyColumn count) conn stmt params++-- | 'queryRawRows_' and 'queryStmtRawRows', each column read by its number with+-- 'Decode.mysqlValue', as a library decoding by column number would.+rawRowApi :: QueryApi+rawRowApi = rawRowApiWith "RawRow API" valueDecoder++-- | 'queryRawRows_' and 'queryStmtRawRows', each column read with the typed+-- decoder for its type, the value put back into the 'MySQLValue' constructor+-- the old API returns, so the same expectations hold.+typedRawRowApi :: QueryApi+typedRawRowApi = rawRowApiWith "typed decoders" typedDecoder++rawRowApiWith :: String -> (ColumnKind -> Decode.FieldDecoder MySQLValue) -> QueryApi+rawRowApiWith name decoderFor = QueryApi+    { apiName            = name+    , apiQuery_          = \_ conn qry -> rawValues decoderFor =<< queryRawRows_ conn qry+    , apiQuery           = \_ conn qry params ->+        rawValues decoderFor =<< queryRawRows_ conn (renderParams qry params)+    , apiQueryVector_    = \_ conn qry -> toVectors =<< rawValues decoderFor =<< queryRawRows_ conn qry+    , apiQueryStmt       = \_ conn stmt params -> rawValues decoderFor =<< queryStmtRawRows conn stmt params+    , apiQueryStmtVector = \_ conn stmt params ->+        toVectors =<< rawValues decoderFor =<< queryStmtRawRows conn stmt params+    }++valueDecoder :: ColumnKind -> Decode.FieldDecoder MySQLValue+valueDecoder _ = Decode.mysqlValue++-- | Each raw row as the list of its columns' values, every column with the+-- parser 'prepareColumn' picks for it.+rawValues :: Decode.RowProtocol protocol+          => (ColumnKind -> Decode.FieldDecoder MySQLValue)+          -> ([ColumnDef], InputStream (Decode.RawRow protocol))+          -> IO ([ColumnDef], InputStream [MySQLValue])+rawValues decoderFor (columns, rows) = do+    parsers <- either throwIO pure+        (traverse (prepareColumn decoderFor) (zip (map Decode.ColumnNumber [0 ..]) columns))+    (,) columns <$> Stream.mapM (rowValues parsers) rows++prepareColumn :: Decode.RowProtocol protocol+              => (ColumnKind -> Decode.FieldDecoder MySQLValue) -> (Decode.ColumnNumber, ColumnDef)+              -> Either Decode.ColumnMismatch (Decode.ColumnNumber, Decode.FieldParser protocol MySQLValue)+prepareColumn decoderFor (column, definition) =+    (,) column <$> Decode.prepareFieldParser (decoderFor (columnKind definition)) column definition++rowValues :: [(Decode.ColumnNumber, Decode.FieldParser protocol MySQLValue)] -> Decode.RawRow protocol+          -> IO [MySQLValue]+rowValues parsers row = traverse (fieldValue row) parsers++fieldValue :: Decode.RawRow protocol -> (Decode.ColumnNumber, Decode.FieldParser protocol MySQLValue)+           -> IO MySQLValue+fieldValue row (column, parser) = Decode.runFieldParser parser row column throwIO pure++toVectors :: ([ColumnDef], InputStream [MySQLValue])+          -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))+toVectors (columns, rows) = (,) (V.fromList columns) <$> Stream.map V.fromList rows
+ test/QueryWithoutResultSet.hs view
@@ -0,0 +1,65 @@+-- | Statements without a result set (INSERT, UPDATE, DELETE) sent through the+-- functions that expect one. The server answers with a lone OK packet, which+-- used to leave these functions blocked forever waiting for column+-- definitions that never come (issue #47).+module QueryWithoutResultSet (tests) where++import           Database.MySQL.Base+import           QueryApi+import qualified Data.Vector       as V+import qualified System.IO.Streams as Stream+import           System.Timeout    (timeout)+import           Test.Tasty+import           Test.Tasty.HUnit++tests :: QueryApi -> TestTree+tests api = testGroup "query functions given an INSERT"+    [ testCase "query_" $ assertEmptyResultSet $ \c -> do+        (columns, rows) <- apiQuery_ api noColumns c "INSERT INTO without_result_set VALUES (1)"+        rowList <- Stream.toList rows+        pure (length columns, length rowList)+    , testCase "queryVector_" $ assertEmptyResultSet $ \c -> do+        (columns, rows) <- apiQueryVector_ api noColumns c "INSERT INTO without_result_set VALUES (1)"+        rowList <- Stream.toList rows+        pure (V.length columns, length rowList)+    , testCase "queryStmt" $ assertEmptyResultSet $ \c -> do+        stmt <- prepareStmt c "INSERT INTO without_result_set VALUES (?)"+        (columns, rows) <- apiQueryStmt api noColumns c stmt [MySQLInt32 1]+        rowList <- Stream.toList rows+        pure (length columns, length rowList)+    , testCase "queryStmtVector" $ assertEmptyResultSet $ \c -> do+        stmt <- prepareStmt c "INSERT INTO without_result_set VALUES (?)"+        (columns, rows) <- apiQueryStmtVector api noColumns c stmt [MySQLInt32 1]+        rowList <- Stream.toList rows+        pure (V.length columns, length rowList)+    ]++-- | An INSERT has no result set, so a row decoder for it reads no columns.+noColumns :: ColumnCount+noColumns = ColumnCount 0++-- | Generous for one INSERT into an empty temporary table, and short enough+-- that a hang fails the suite instead of stalling CI.+replyTimeLimitMicroseconds :: Int+replyTimeLimitMicroseconds = 10000000++-- | Runs the insert on a fresh connection; it reports the column and row counts+-- of the result set it got back. Both must be zero, the row must be inserted+-- exactly once, and the connection must stay usable afterwards.+assertEmptyResultSet :: (MySQLConn -> IO (Int, Int)) -> Assertion+assertEmptyResultSet runInsert = do+    (_, c) <- connectDetail defaultConnectInfo+        { ciUser = "testMySQLHaskell"+        , ciDatabase = "testMySQLHaskell"+        }+    _ <- execute_ c "CREATE TEMPORARY TABLE without_result_set (__id INT)"+    outcome <- timeout replyTimeLimitMicroseconds (runInsert c)+    case outcome of+        Nothing -> assertFailure+            "blocked for 10 s waiting for a result set the server never sends"+        Just columnsAndRows -> do+            assertEqual "columns and rows returned" (0, 0) columnsAndRows+            (_, rows) <- query_ c "SELECT COUNT(*) FROM without_result_set"+            counts <- Stream.toList rows+            assertEqual "the row was inserted once" [[MySQLInt64 1]] counts+    close c
+ test/ResetConnection.hs view
@@ -0,0 +1,64 @@+-- | 'resetConnection' gives a connection the session of a new one, so a pool+-- can hand it to its next user.+module ResetConnection (tests) where++import           Control.Exception   (try)+import           Control.Monad       (void)+import           Data.Word           (Word16)+import           Database.MySQL.Base+import qualified System.IO.Streams   as Stream+import           Test.Tasty+import           Test.Tasty.HUnit+import           TestUser            (withTestConnection)++tests :: TestTree+tests = testGroup "resetConnection"+    [ testCase "a user variable is NULL afterwards" $+        withTestConnection $ \c -> do+            void (execute_ c "SET @reset_probe = 42")+            resetConnection c+            selectAll c "SELECT @reset_probe" >>= assertEqual "the variable" [[MySQLNull]]+    , testCase "a temporary table is gone afterwards" $+        withTestConnection $ \c -> do+            void (execute_ c "CREATE TEMPORARY TABLE reset_connection_temporary (x INT)")+            resetConnection c+            -- ER_NO_SUCH_TABLE+            assertRefused 1146 (selectAll c "SELECT x FROM reset_connection_temporary")+    , testCase "an uncommitted insert is rolled back" $+        withTestConnection $ \c -> do+            void (execute_ c "DROP TABLE IF EXISTS reset_connection_rollback")+            void (execute_ c "CREATE TABLE reset_connection_rollback (x INT) ENGINE=InnoDB")+            void (execute_ c "START TRANSACTION")+            void (execute_ c "INSERT INTO reset_connection_rollback VALUES (1)")+            resetConnection c+            selectAll c "SELECT COUNT(*) FROM reset_connection_rollback"+                >>= assertEqual "rows in the table" [[MySQLInt64 0]]+            void (execute_ c "DROP TABLE reset_connection_rollback")+    , testCase "a statement prepared before it is refused by the server" $+        withTestConnection $ \c -> do+            stmt <- prepareStmt c "SELECT 'prepared'"+            resetConnection c+            -- ER_UNKNOWN_STMT_HANDLER+            assertRefused 1243 (queryStmt c stmt [] >>= Stream.toList . snd)+            selectAll c "SELECT 'next'" >>= assertEqual "the next query's rows" [[MySQLText "next"]]+    , testCase "with rows unread it refuses instead of reading a row as its reply" $+        withTestConnection $ \c -> do+            void (execute_ c "SET @reset_probe = 42")+            (_, unread) <- query_ c "SELECT 'row' UNION ALL SELECT 'row'"+            refused <- try (resetConnection c)+            case refused of+                Left UnconsumedResultSet -> pure ()+                Right () -> assertFailure "resetConnection read a row packet as its reply"+            Stream.skipToEof unread+            selectAll c "SELECT @reset_probe" >>= assertEqual "the session kept its state" [[MySQLInt64 42]]+    ]++selectAll :: MySQLConn -> Query -> IO [[MySQLValue]]+selectAll c q = query_ c q >>= Stream.toList . snd++assertRefused :: Show a => Word16 -> IO a -> Assertion+assertRefused code action = do+    outcome <- try action+    case outcome of+        Left (ERRException err) -> assertEqual ("error " ++ show (errMsg err)) code (errCode err)+        Right answer -> assertFailure ("the server answered " ++ show answer)
+ test/ResultCharsets.hs view
@@ -0,0 +1,51 @@+-- | String columns in the character set the server sent them in. latin1 bytes+-- used to go through a UTF-8 decoder that threw an impure exception. Bytes+-- that are not UTF-8 in any other charset raise 'InvalidUtf8Text' when the+-- value is used, not while the row is read (1.4.0 refused the row).+module ResultCharsets (tests) where++import           Control.Exception              (evaluate, try)+import           Data.Binary.Parser             (parseOnly)+import           Data.Binary.Put                (runPut)+import qualified Data.ByteString                as B+import qualified Data.ByteString.Lazy           as L+import           Data.Word                      (Word16)+import           Database.MySQL.Protocol.ColumnDef+import           Database.MySQL.Protocol.MySQLValue+import           Database.MySQL.Protocol.Packet  (putLenEncBytes)+import           Test.Tasty+import           Test.Tasty.HUnit++tests :: TestTree+tests = testGroup "result character sets"+    [ testCase "latin1 (collation 8) is Windows-1252" $+        assertEqual "value" (Right (MySQLText "\x20AC\x00E9\x0081"))+            (parseOnly (getTextField (varchar latin1SwedishCi)) (lenEnc "\x80\xE9\x81"))+    , testCase "another charset decodes ASCII, and other bytes raise when the value is used" $ do+        assertEqual "ASCII value" (Right (MySQLText "abc"))+            (parseOnly (getBinaryField (varchar gbkChineseCi)) (lenEnc "abc"))+        text <- either (assertFailure . ("refused the field: " <>)) pure+            (parseOnly (getTextField (varchar gbkChineseCi)) (lenEnc "\xC4\xE3"))+        used <- try (evaluate text)+        used @?= Left (InvalidUtf8Text gbkChineseCi "\xC4\xE3")+    , testCase "a text row with one invalid string decodes, and only that value raises" $ do+        let kinds = map columnKind [varchar gbkChineseCi, varchar gbkChineseCi]+        values <- either (assertFailure . ("refused the row: " <>) . show) pure+            (decodeTextRow kinds (lenEnc "ok" <> lenEnc "\xC4\xE3"))+        case values of+            [ok, invalid] -> do+                ok @?= MySQLText "ok"+                used <- try (evaluate invalid)+                used @?= Left (InvalidUtf8Text gbkChineseCi "\xC4\xE3")+            other -> assertFailure ("expected two values, got " <> show (length other))+    ]++latin1SwedishCi, gbkChineseCi :: Word16+latin1SwedishCi = 8+gbkChineseCi = 28++varchar :: Word16 -> ColumnDef+varchar charset = ColumnDef "" "" "" "c" "c" charset 0 mySQLTypeVarString 0 0++lenEnc :: B.ByteString -> B.ByteString+lenEnc value = L.toStrict (runPut (putLenEncBytes value))
+ test/RoundtripBit.hs view
@@ -0,0 +1,47 @@+module RoundtripBit (tests) where++import Database.MySQL.Base+import QueryApi+import qualified System.IO.Streams as Stream+import Test.Tasty+import Test.Tasty.HUnit++tests :: QueryApi -> TestTree+tests api = testCaseSteps "roundtrip MySQLBit" $ \step -> do+    (_, c) <- connectDetail defaultConnectInfo+        { ciUser = "testMySQLHaskell"+        , ciDatabase = "testMySQLHaskell"+        }++    execute_ c "CREATE TEMPORARY TABLE test_bit (__id INT, __val BIT(16))"+    execute_ c "INSERT INTO test_bit VALUES (1, NULL)"++    step "roundtrip MySQLBit via binary protocol"+    updStmt <- prepareStmt c "UPDATE test_bit SET __val = ? WHERE __id = 1"+    selStmt <- prepareStmt c "SELECT __val FROM test_bit WHERE __id = 1"++    let bitVal = 43744 -- 0b1010101011100000+    executeStmt c updStmt [MySQLBit bitVal]++    (_, is) <- apiQueryStmt api (ColumnCount 1) c selStmt []+    Just [v] <- Stream.read is+    Stream.skipToEof is+    assertEqual "MySQLBit roundtrips through BIT(16)" (MySQLBit bitVal) v++    step "roundtrip MySQLBit zero"+    executeStmt c updStmt [MySQLBit 0]++    (_, is2) <- apiQueryStmt api (ColumnCount 1) c selStmt []+    Just [v2] <- Stream.read is2+    Stream.skipToEof is2+    assertEqual "MySQLBit 0 roundtrips" (MySQLBit 0) v2++    step "roundtrip MySQLBit max for BIT(16)"+    executeStmt c updStmt [MySQLBit 65535]++    (_, is3) <- apiQueryStmt api (ColumnCount 1) c selStmt []+    Just [v3] <- Stream.read is3+    Stream.skipToEof is3+    assertEqual "MySQLBit 65535 roundtrips" (MySQLBit 65535) v3++    close c
+ test/RoundtripYear.hs view
@@ -0,0 +1,54 @@+module RoundtripYear (tests) where++import Database.MySQL.Base+import QueryApi+import qualified System.IO.Streams as Stream+import Test.Tasty+import Test.Tasty.HUnit++tests :: QueryApi -> TestTree+tests api = testCaseSteps "roundtrip MySQLYear" $ \step -> do+    (_, c) <- connectDetail defaultConnectInfo+        { ciUser = "testMySQLHaskell"+        , ciDatabase = "testMySQLHaskell"+        }++    execute_ c "CREATE TEMPORARY TABLE test_year (__id INT, __val YEAR(4))"+    execute_ c "INSERT INTO test_year VALUES (1, NULL)"++    step "roundtrip MySQLYear via binary protocol"+    updStmt <- prepareStmt c "UPDATE test_year SET __val = ? WHERE __id = 1"+    selStmt <- prepareStmt c "SELECT __val FROM test_year WHERE __id = 1"++    executeStmt c updStmt [MySQLYear 1999]++    (_, is) <- apiQueryStmt api (ColumnCount 1) c selStmt []+    Just [v] <- Stream.read is+    Stream.skipToEof is+    assertEqual "MySQLYear 1999 roundtrips" (MySQLYear 1999) v++    step "roundtrip MySQLYear min (1901)"+    executeStmt c updStmt [MySQLYear 1901]++    (_, is2) <- apiQueryStmt api (ColumnCount 1) c selStmt []+    Just [v2] <- Stream.read is2+    Stream.skipToEof is2+    assertEqual "MySQLYear 1901 roundtrips" (MySQLYear 1901) v2++    step "roundtrip MySQLYear max (2155)"+    executeStmt c updStmt [MySQLYear 2155]++    (_, is3) <- apiQueryStmt api (ColumnCount 1) c selStmt []+    Just [v3] <- Stream.read is3+    Stream.skipToEof is3+    assertEqual "MySQLYear 2155 roundtrips" (MySQLYear 2155) v3++    step "roundtrip MySQLYear zero"+    executeStmt c updStmt [MySQLYear 0]++    (_, is4) <- apiQueryStmt api (ColumnCount 1) c selStmt []+    Just [v4] <- Stream.read is4+    Stream.skipToEof is4+    assertEqual "MySQLYear 0 roundtrips" (MySQLYear 0) v4++    close c
+ test/RowDecoderQueries.hs view
@@ -0,0 +1,144 @@+-- | 'queryRows_', 'queryStmtRows' and 'queryRawRows_' against a server. The+-- typed decoders must give what 'query_' and 'queryStmt' give through+-- "Database.MySQL.Field", and a result set a decoder does not fit, or a field+-- it cannot decode, must leave the connection usable.+module RowDecoderQueries (tests) where++import           Control.Exception      (try)+import           Data.Int               (Int32)+import           Data.Scientific        (Scientific)+import           Data.Text              (Text)+import           Data.Time.Calendar     (Day, fromGregorian)+import           Data.Time.LocalTime    (LocalTime (..), TimeOfDay (..))+import           Database.MySQL.Base+import qualified Database.MySQL.Decoder as Decode+import           Database.MySQL.Field+import qualified System.IO.Streams      as Stream+import           System.Timeout         (timeout)+import           Test.Tasty+import           Test.Tasty.HUnit++type Row = (Int32, Text, Day, LocalTime, Double, Scientific, Maybe Int32, Bool, TimeOfDay)++rowDecoder :: Decode.RowDecoder Row+rowDecoder = (,,,,,,,,)+    <$> Decode.field Decode.int32+    <*> Decode.field Decode.text+    <*> Decode.field Decode.day+    <*> Decode.field Decode.localTime+    <*> Decode.field Decode.double+    <*> Decode.field Decode.scientific+    <*> Decode.field (Decode.nullable Decode.int32)+    <*> Decode.field Decode.bool+    <*> Decode.field Decode.timeOfDay++expectedRows :: [Row]+expectedRows =+    [ ( 1, "Jappie", fromGregorian 1990 1 2+      , LocalTime (fromGregorian 2026 10 8) (TimeOfDay 12 34 56.789012)+      , 0.25, 12.5, Nothing, True, TimeOfDay 12 0 1 )+    , ( 2, "café", fromGregorian 2000 2 29+      , LocalTime (fromGregorian 2000 2 29) (TimeOfDay 23 59 59.000001)+      , -1.5, -3.25, Just 7, False, TimeOfDay 838 59 59 )+    ]++-- | The same row through 'MySQLValue' and "Database.MySQL.Field".+viaField :: [MySQLValue] -> Either String Row+viaField values = case values of+    [number, name, born, created, ratio, amount, count, flag, duration] ->+        either (Left . show) Right $ (,,,,,,,,)+            <$> decodeInt32 number+            <*> decodeText name+            <*> decodeDay born+            <*> decodeLocalTime created+            <*> decodeDouble ratio+            <*> decodeScientific amount+            <*> decodeMaybe count+            <*> decodeBool flag+            <*> decodeTimeOfDay duration+    _ -> Left ("expected 9 values, got " ++ show values)++tests :: TestTree+tests = testGroup "decoding rows without MySQLValue"+    [ testCase "queryRows_ gives what query_ and Field give" $ withRows $ \c -> do+        (_, rows) <- queryRows_ rowDecoder c "SELECT * FROM decoded_rows ORDER BY id"+        decoded <- Stream.toList rows+        (_, values) <- query_ c "SELECT * FROM decoded_rows ORDER BY id"+        throughField <- Stream.toList values+        decoded @?= expectedRows+        traverse viaField throughField @?= Right expectedRows+    , testCase "queryStmtRows gives what queryStmt and Field give" $ withRows $ \c -> do+        stmt <- prepareStmt c "SELECT * FROM decoded_rows WHERE id >= ? ORDER BY id"+        (_, rows) <- queryStmtRows rowDecoder c stmt [MySQLInt32 0]+        decoded <- Stream.toList rows+        (_, values) <- queryStmt c stmt [MySQLInt32 0]+        throughField <- Stream.toList values+        decoded @?= expectedRows+        traverse viaField throughField @?= Right expectedRows+    , testCase "queryRawRows_ rows decode by column number" $ withRows $ \c -> do+        (columns, rows) <- queryRawRows_ c "SELECT id, name FROM decoded_rows ORDER BY id"+        parser <- either (assertFailure . show) pure+            (Decode.prepareFieldParser Decode.text (Decode.ColumnNumber 1) (columns !! 1))+        names <- Stream.toList =<< Stream.mapM+            (\row -> Decode.runFieldParser parser row (Decode.ColumnNumber 1) (assertFailure . show) pure) rows+        names @?= ["Jappie", "café"]+    , testCase "a column the decoder does not take raises before any row" $ withRows $ \c -> do+        outcome <- try (queryRows_ (Decode.field Decode.text) c "SELECT id FROM decoded_rows")+        case outcome of+            Left mismatch -> mismatch @?= Decode.ColumnTypeMismatch (Decode.ColumnNumber 0) "id" mySQLTypeLong "Text"+            Right _       -> assertFailure "the INT column was accepted as Text"+        assertRowCount c+    , testCase "a decoder for another number of columns raises before any row" $ withRows $ \c -> do+        stmt <- prepareStmt c "SELECT id, name FROM decoded_rows"+        outcome <- try (queryStmtRows (Decode.field Decode.int32) c stmt [])+        case outcome of+            Left mismatch -> mismatch @?= Decode.ColumnCountMismatch 1 2+            Right _       -> assertFailure "a 2-column result set fit a 1-column decoder"+        assertRowCount c+    , testCase "a field that does not decode raises and skips the other rows" $ withRows $ \c -> do+        (_, rows) <- queryRows_ (Decode.field Decode.int32) c+            "SELECT maybe_count FROM decoded_rows ORDER BY id"+        outcome <- try (Stream.toList rows)+        case outcome of+            Left fieldError -> fieldError @?= Decode.FieldError (Decode.ColumnNumber 0) Decode.FieldUnexpectedNull+            Right counts    -> assertFailure ("the NULL decoded: " ++ show counts)+        assertRowCount c+    , testCase "an INSERT gives no columns and no rows" $ withRows $ \c -> do+        (columns, rows) <- queryRows_ rowDecoder c+            "INSERT INTO decoded_rows (id, name, born, created, ratio, amount, flag, duration) \+            \VALUES (3, 'x', '2001-01-01', '2001-01-01', 0, 0, 0, '00:00:00')"+        decoded <- Stream.toList rows+        (length columns, length decoded) @?= (0, 0)+    ]++-- | Generous for a few statements on a two-row temporary table, and short+-- enough that a hang fails the suite instead of stalling CI.+replyTimeLimitMicroseconds :: Int+replyTimeLimitMicroseconds = 10000000++withRows :: (MySQLConn -> Assertion) -> Assertion+withRows body = do+    (_, c) <- connectDetail defaultConnectInfo+        { ciUser = "testMySQLHaskell"+        , ciDatabase = "testMySQLHaskell"+        }+    _ <- execute_ c+        "CREATE TEMPORARY TABLE decoded_rows (\+        \id INT, name VARCHAR(20), born DATE, created DATETIME(6), ratio DOUBLE, \+        \amount DECIMAL(10, 2), maybe_count INT NULL, flag TINYINT(1), duration TIME\+        \) CHARACTER SET utf8mb4"+    _ <- execute_ c+        "INSERT INTO decoded_rows VALUES \+        \(1, 'Jappie', '1990-01-02', '2026-10-08 12:34:56.789012', 0.25, 12.50, NULL, 1, '12:00:01'), \+        \(2, 'café', '2000-02-29', '2000-02-29 23:59:59.000001', -1.5, -3.25, 7, 0, '838:59:59')"+    finished <- timeout replyTimeLimitMicroseconds (body c)+    case finished of+        Nothing -> assertFailure "blocked for 10 s waiting for a reply"+        Just () -> close c++-- | The connection must be back in step: a fresh query gets its own rows.+assertRowCount :: MySQLConn -> Assertion+assertRowCount c = do+    (_, rows) <- query_ c "SELECT COUNT(*) FROM decoded_rows"+    counts <- Stream.toList rows+    assertEqual "rows of the next query" [[MySQLInt64 2]] counts
+ test/SelectOne.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE ScopedTypeVariables #-}++module SelectOne (tests) where++import Database.MySQL.Base+import QueryApi+import qualified System.IO.Streams as Stream+import Test.Tasty+import Test.Tasty.HUnit++tests :: QueryApi -> TestTree+tests api = testCaseSteps "select 1" $ \step -> do+    step "connecting..."+    (_, c) <- connectDetail defaultConnectInfo+        { ciUser = "testMySQLHaskell"+        , ciDatabase = "testMySQLHaskell"+        }++    step "executing SELECT 1..."+    (_, is) <- apiQuery_ api (ColumnCount 1) c "SELECT 1"+    Just row <- Stream.read is+    -- MySQL 8.0 returns MySQLInt64 for integer literals, older versions return MySQLInt32+    assertBool "SELECT 1 returns 1" (row == [MySQLInt32 1] || row == [MySQLInt64 1])+    Stream.skipToEof is++    close c
+ test/Sha1Scramble.hs view
@@ -0,0 +1,49 @@+module Sha1Scramble (tests) where++import qualified Data.ByteString as B+import qualified Data.ByteString.Base16 as Base16+import           Data.ByteString.Char8 (pack)+import           Database.MySQL.Connection (scrambleSHA1)+import           Test.Tasty+import           Test.Tasty.HUnit++hexToBytes :: String -> B.ByteString+hexToBytes = Base16.decodeLenient . pack++tests :: TestTree+tests = testGroup "SHA1 Scramble"+    [ testCase "empty password returns empty" $ do+        let result = scrambleSHA1 "some_salt" ""+        assertEqual "empty password" B.empty result++    , testCase "non-empty password returns 20 bytes" $ do+        let result = scrambleSHA1 "12345678901234567890" "password"+        assertEqual "scramble length" 20 (B.length result)++    , testCase "different salts produce different scrambles" $ do+        let r1 = scrambleSHA1 "salt1_______________" "password"+            r2 = scrambleSHA1 "salt2_______________" "password"+        assertBool "different salts should produce different results" (r1 /= r2)++    , testCase "different passwords produce different scrambles" $ do+        let salt = "12345678901234567890"+            r1 = scrambleSHA1 salt "password1"+            r2 = scrambleSHA1 salt "password2"+        assertBool "different passwords should produce different results" (r1 /= r2)++    , testCase "scramble is deterministic" $ do+        let salt = "12345678901234567890"+            r1 = scrambleSHA1 salt "testPassword123"+            r2 = scrambleSHA1 salt "testPassword123"+        assertEqual "same inputs produce same output" r1 r2++    , testCase "golden: salt=12345678901234567890 pass=password" $ do+        let result = scrambleSHA1 "12345678901234567890" "password"+            expected = hexToBytes "1957dce2724282e018f40d905824cb6361f88d41"+        assertEqual "golden vector 1" expected result++    , testCase "golden: salt=ABCDEFGHIJKLMNOPQRST pass=secret" $ do+        let result = scrambleSHA1 "ABCDEFGHIJKLMNOPQRST" "secret"+            expected = hexToBytes "28441590674285e7d03cae7af237504797f70e91"+        assertEqual "golden vector 2" expected result+    ]
+ test/Sha256Scramble.hs view
@@ -0,0 +1,49 @@+module Sha256Scramble (tests) where++import qualified Data.ByteString as B+import qualified Data.ByteString.Base16 as Base16+import           Data.ByteString.Char8 (pack)+import           Database.MySQL.Connection (scrambleSHA256)+import           Test.Tasty+import           Test.Tasty.HUnit++hexToBytes :: String -> B.ByteString+hexToBytes = Base16.decodeLenient . pack++tests :: TestTree+tests = testGroup "SHA256 Scramble"+    [ testCase "empty password returns empty" $ do+        let result = scrambleSHA256 "some_salt" ""+        assertEqual "empty password" B.empty result++    , testCase "non-empty password returns 32 bytes" $ do+        let result = scrambleSHA256 "12345678901234567890" "password"+        assertEqual "scramble length" 32 (B.length result)++    , testCase "different salts produce different scrambles" $ do+        let r1 = scrambleSHA256 "salt1_______________" "password"+            r2 = scrambleSHA256 "salt2_______________" "password"+        assertBool "different salts should produce different results" (r1 /= r2)++    , testCase "different passwords produce different scrambles" $ do+        let salt = "12345678901234567890"+            r1 = scrambleSHA256 salt "password1"+            r2 = scrambleSHA256 salt "password2"+        assertBool "different passwords should produce different results" (r1 /= r2)++    , testCase "scramble is deterministic" $ do+        let salt = "12345678901234567890"+            r1 = scrambleSHA256 salt "testPassword123"+            r2 = scrambleSHA256 salt "testPassword123"+        assertEqual "same inputs produce same output" r1 r2++    , testCase "golden: salt=12345678901234567890 pass=password" $ do+        let result = scrambleSHA256 "12345678901234567890" "password"+            expected = hexToBytes "718d80fb92b693f7ee1fcad95bfe7a2be1f332ac2c3ec6cdc85893b65b240650"+        assertEqual "golden vector 1" expected result++    , testCase "golden: salt=ABCDEFGHIJKLMNOPQRST pass=secret" $ do+        let result = scrambleSHA256 "ABCDEFGHIJKLMNOPQRST" "secret"+            expected = hexToBytes "d721e183c1f036a196c1389201b8d53f38064a16f1d0923683ab886763152d75"+        assertEqual "golden vector 2" expected result+    ]
+ test/StringLiterals.hs view
@@ -0,0 +1,99 @@+-- | Text and bytes rendered into the query text as single-quoted string+-- literals, read back the way the server's lexer reads them, with and+-- without NO_BACKSLASH_ESCAPES.+module StringLiterals (tests) where++import           Data.ByteString                    (ByteString)+import qualified Data.ByteString                    as B+import qualified Data.ByteString.Lazy               as L+import           Data.Maybe                         (isJust)+import           Data.Text                          (Text)+import qualified Data.Text                          as T+import qualified Data.Text.Encoding                 as T+import           Data.Word                          (Word8)+import           Database.MySQL.Base+import           Test.QuickCheck                    (Gen, arbitrary, elements,+                                                     forAll, frequency, listOf)+import           Test.Tasty+import           Test.Tasty.HUnit                   (testCase, (@?=))+import           Test.Tasty.QuickCheck              (testProperty, (===))++tests :: TestTree+tests = testGroup "string literals"+    [ testCase "a quote and a backslash are doubled, other characters kept" $+        rendered (MySQLText "it's\n\"C:\\\"") @?= "'it''s\n\"C:\\\\\"'"+    , testGroup "default sql_mode"+        [ testProperty "bytes read back exactly" $ forAll genBytes $ \bytes ->+            readLiteral BackslashEscapes (rendered (MySQLBytes bytes)) === Just bytes+        , testProperty "text reads back exactly" $ forAll genText $ \text ->+            readLiteral BackslashEscapes (rendered (MySQLText text)) === Just (T.encodeUtf8 text)+        ]+    , testGroup "NO_BACKSLASH_ESCAPES"+        [ testProperty "bytes end the literal at its closing quote" $ forAll genBytes $ \bytes ->+            isJust (readLiteral NoBackslashEscapes (rendered (MySQLBytes bytes)))+        , testProperty "text ends the literal at its closing quote" $ forAll genText $ \text ->+            isJust (readLiteral NoBackslashEscapes (rendered (MySQLText text)))+        , testProperty "bytes without a backslash read back exactly" $+            forAll (B.filter (/= backslash) <$> genBytes) $ \bytes ->+                readLiteral NoBackslashEscapes (rendered (MySQLBytes bytes)) === Just bytes+        , testProperty "text without a backslash reads back exactly" $+            forAll (T.filter (/= '\\') <$> genText) $ \text ->+                readLiteral NoBackslashEscapes (rendered (MySQLText text)) === Just (T.encodeUtf8 text)+        ]+    ]++rendered :: MySQLValue -> ByteString+rendered value = L.toStrict (fromQuery (renderParams "?" [value]))++data BackslashMode = BackslashEscapes | NoBackslashEscapes++-- | The value the server reads from a whole single-quoted literal, following+-- <https://dev.mysql.com/doc/refman/8.0/en/string-literals.html>; Nothing+-- when the literal ends before the input does, or not at all. Reading bytes+-- is exact for UTF-8, where a quote or backslash byte never belongs to a+-- longer character.+readLiteral :: BackslashMode -> ByteString -> Maybe ByteString+readLiteral mode literal = case B.unpack literal of+    [] -> Nothing+    open : body -> if open == quote then B.pack <$> readBody mode body else Nothing++readBody :: BackslashMode -> [Word8] -> Maybe [Word8]+readBody mode body = case body of+    [] -> Nothing+    byte : rest -> if+        | byte == quote -> case rest of+            [] -> Just []+            next : afterNext ->+                if next == quote then (quote :) <$> readBody mode afterNext else Nothing+        | byte == backslash -> case mode of+            NoBackslashEscapes -> (backslash :) <$> readBody mode rest+            BackslashEscapes -> case rest of+                [] -> Nothing+                escaped : afterEscaped -> (escapedBytes escaped ++) <$> readBody mode afterEscaped+        | otherwise -> (byte :) <$> readBody mode rest++-- | What a backslash followed by this byte stands for in the default mode.+escapedBytes :: Word8 -> [Word8]+escapedBytes escaped = case lookup escaped escapeSequences of+    Just meaning -> [meaning]+    Nothing -> if escaped == 0x25 || escaped == 0x5F then [backslash, escaped] else [escaped]++-- | @\\0 \\b \\n \\r \\t \\Z@; @\\%@ and @\\_@ keep their backslash, any other+-- escaped byte stands for itself.+escapeSequences :: [(Word8, Word8)]+escapeSequences = [(0x30, 0), (0x62, 8), (0x6E, 10), (0x72, 13), (0x74, 9), (0x5A, 26)]++quote, backslash :: Word8+quote = 0x27+backslash = 0x5C++-- | Half the bytes are ones the escaping or the lexer treats specially.+genBytes :: Gen ByteString+genBytes = B.pack <$> listOf (frequency+    [(1, elements (B.unpack (T.encodeUtf8 (T.pack specialCharacters)))), (1, arbitrary)])++genText :: Gen Text+genText = T.pack <$> listOf (frequency [(1, elements specialCharacters), (1, arbitrary)])++specialCharacters :: String+specialCharacters = "\0\b\t\n\r\SUB\"%'\\_"
+ test/TCPStreams.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE ScopedTypeVariables #-}++module TCPStreams (tests) where++------------------------------------------------------------------------------+import           Control.Concurrent             (forkIO, newEmptyMVar, putMVar,+                                                 takeMVar)+import qualified Network.Socket                 as N+import           System.Timeout                 (timeout)+import           Test.Tasty+import           Test.Tasty.HUnit+import qualified Data.ByteString                as B+import qualified Data.ByteString.Lazy           as L+------------------------------------------------------------------------------+import qualified Data.TLSSetting                as TLS+import           Data.Connection+import qualified System.IO.Streams              as Stream+import qualified System.IO.Streams.TCP          as TCP+import qualified System.IO.Streams.TLS          as TLS+------------------------------------------------------------------------------++import Test.Tasty.ExpectedFailure (ignoreTestBecause)++tests :: TestTree+tests = testGroup "tests" [ testGroup "TCP" tcpTests+        , testGroup "TLS"  tlsTests+        , testGroup "HTTPS" [testHTTPS]+        ]++------------------------------------------------------------------------------++tcpTests :: [TestTree]+tcpTests = [ testTCPSocket ]++testTCPSocket :: TestTree+testTCPSocket = testCase "network/socket" $+    N.withSocketsDo $ do+    x <- timeout (10 * 10^(6::Int)) go+    assertEqual "ok" (Just ()) x++  where+    go = do+        portMVar   <- newEmptyMVar+        resultMVar <- newEmptyMVar+        _ <- forkIO $ client portMVar resultMVar+        server portMVar+        l <- takeMVar resultMVar+        assertEqual "testSocket" l ["ok"]++    client mvar resultMVar = do+        _ <- takeMVar mvar+        conn <- TCP.connect "127.0.0.1" 8888+        send conn "ok"+        Stream.toList (source conn) >>= putMVar resultMVar+        close conn++    server mvar = do+        sock <- TCP.bindAndListen 1024 8888+        putMVar mvar ()+        conn <- TCP.accept sock+        req <- Stream.readExactly 2 (source conn)+        send conn (L.fromStrict req)+        close conn++------------------------------------------------------------------------------++tlsTests :: [TestTree]+tlsTests = [ testTLSSocket+             -- TODO disabled because won't work in a  nix container+             -- , testHTTPS+           ]++testTLSSocket :: TestTree+testTLSSocket = testCase "network/socket" $+    N.withSocketsDo $ do+    x <- timeout (10 * 10^(6::Int)) go+    assertEqual "ok" (Just ()) x++  where+    go = do+        portMVar   <- newEmptyMVar+        resultMVar <- newEmptyMVar+        _ <- forkIO $ client portMVar resultMVar+        server portMVar+        l <- takeMVar resultMVar+        assertEqual "testSocket" l ["ok"]++    client mvar resultMVar = do+        _ <- takeMVar mvar+        cp <- TLS.makeClientParams (TLS.CustomCAStore "./test/cert/ca.pem")+        conn <- TLS.connect cp (Just "Winter") "127.0.0.1" 8889+        send conn "ok"+        Stream.toList (source conn) >>= putMVar resultMVar+        close conn++    server mvar = do+        sp <- TLS.makeServerParams "./test/cert/server-cert.pem" [] "./test/cert/server-key.pem"+        sock <- TCP.bindAndListen 1024 8889+        putMVar mvar ()+        conn <- TLS.accept sp sock+        req <- Stream.readExactly 2 (source conn)+        send conn (L.fromStrict req)+        close conn++testHTTPS :: TestTree+testHTTPS =+  ignoreTestBecause "can't connect to socket in a nix container, the build becomes no longer reproducible." $+  testCase "network/https" $+    N.withSocketsDo $ do+    x <- timeout (10 * 10^(6::Int)) go+    assertEqual "ok" (Just 1024) x+  where+    go = do+        cp <- TLS.makeClientParams TLS.MozillaCAStore+        conn <- TLS.connect cp Nothing "www.bing.com" 443+        send conn ("GET / HTTP/1.1\r\n")+        send conn ("Host: www.bing.com\r\n")+        send conn ("\r\n")+        bs <- Stream.readExactly 1024 (source conn)+        close conn+        return (B.length bs)
+ test/TLSClientCertificate.hs view
@@ -0,0 +1,40 @@+-- | Logging in as an account that requires a client certificate, which+-- 'makeClientParams'' loaded but never sent before 1.5.0.+module TLSClientCertificate (tests) where++import           Control.Exception     (bracket_)+import           Database.MySQL.Base+import qualified Database.MySQL.TLS    as TLS+import           Database.MySQL.TLS    (makeClientParams', TrustedCAStore(..))+import qualified System.IO.Streams     as Stream+import           System.FilePath       (replaceFileName)+import           Test.Tasty+import           Test.Tasty.HUnit+import           TestUser              (withTestConnection)++-- | The certificate and key sit next to the CA at @caPath@, which signed them.+tests :: FilePath -> TestTree+tests caPath = testCase "an account requiring a client certificate logs in" $+    withTestConnection $ \admin -> bracket_+        (execute_ admin createCertificateUser)+        (execute_ admin "DROP USER 'tlsClientCert'@'localhost'") $ do+            params <- makeClientParams'+                (replaceFileName caPath "client-cert.pem")+                []+                (replaceFileName caPath "client-key.pem")+                (CustomCAStore caPath)+            conn <- TLS.connect certificateConnectInfo (params, "Winter")+            (_, rows) <- query_ conn "SELECT CURRENT_USER()"+            loggedInAs <- Stream.toList rows+            close conn+            loggedInAs @?= [[MySQLText "tlsClientCert@localhost"]]++-- | The subject is the one test/cert/client-cert.pem carries, so only that+-- certificate opens the account.+createCertificateUser :: Query+createCertificateUser =+    "CREATE USER 'tlsClientCert'@'localhost' IDENTIFIED WITH mysql_native_password \+    \REQUIRE SUBJECT '/O=mysql-haskell/CN=testClientCert'"++certificateConnectInfo :: ConnectInfo+certificateConnectInfo = defaultConnectInfo { ciUser = "tlsClientCert", ciDatabase = "" }
+ test/TLSConnection.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE ScopedTypeVariables #-}++module TLSConnection (tests) where++import qualified Data.ByteString       as B+import qualified Data.Text             as T+import           Database.MySQL.Base+import           QueryApi+import qualified Database.MySQL.TLS    as TLS+import           Database.MySQL.TLS    (makeClientParams, TrustedCAStore(..))+import qualified System.IO.Streams     as Stream+import           Test.Tasty+import           Test.Tasty.HUnit++tests :: QueryApi -> FilePath -> TestTree+tests api caPath = testGroup "tls-connection"+    [ testCaseSteps "TLS connectDetail: SELECT 1" $ \step -> do+        step "creating TLS client params..."+        cparams <- makeClientParams (CustomCAStore caPath)++        step "connecting via TLS..."+        (greet, conn) <- TLS.connectDetail+            defaultConnectInfo { ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell" }+            (cparams, "Winter")++        step "checking greeting version..."+        let ver = greetingVersion greet+        assertBool "greeting version is not empty" (not $ B.null ver)++        step "executing SELECT 1..."+        (_, is) <- apiQuery_ api (ColumnCount 1) conn "SELECT 1"+        Just row <- Stream.read is+        assertBool "SELECT 1 returns 1"+            (row == [MySQLInt32 1] || row == [MySQLInt64 1])+        Stream.skipToEof is++        close conn++    , testCaseSteps "TLS connection: verify encryption active" $ \step -> do+        step "creating TLS client params..."+        cparams <- makeClientParams (CustomCAStore caPath)++        step "connecting via TLS..."+        conn <- TLS.connect+            defaultConnectInfo { ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell" }+            (cparams, "Winter")++        step "checking SSL cipher..."+        (_, is) <- apiQuery_ api (ColumnCount 2) conn "SHOW STATUS LIKE 'Ssl_cipher'"+        Just row <- Stream.read is+        case row of+            [_name, MySQLText cipher] ->+                assertBool "SSL cipher is not empty" (not $ T.null cipher)+            _ -> assertFailure ("unexpected row shape: " ++ show row)+        Stream.skipToEof is++        close conn++    , testCaseSteps "TLS connection: prepared statement roundtrip" $ \step -> do+        step "creating TLS client params..."+        cparams <- makeClientParams (CustomCAStore caPath)++        step "connecting via TLS..."+        conn <- TLS.connect+            defaultConnectInfo { ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell" }+            (cparams, "Winter")++        step "executing prepared statement..."+        stmt <- prepareStmt conn "SELECT ? + 1"+        (_, is) <- apiQueryStmt api (ColumnCount 1) conn stmt [MySQLInt32 41]+        Just row <- Stream.read is+        assertBool "41 + 1 = 42"+            (row == [MySQLInt32 42] || row == [MySQLInt64 42])+        Stream.skipToEof is++        close conn+    ]
+ test/TestUser.hs view
@@ -0,0 +1,12 @@+-- | The account nix/ci.nix creates for the integration suite.+module TestUser (testConnectInfo, withTestConnection) where++import           Control.Exception   (bracket)+import           Database.MySQL.Base++testConnectInfo :: ConnectInfo+testConnectInfo = defaultConnectInfoMB4+    { ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell" }++withTestConnection :: (MySQLConn -> IO a) -> IO a+withTestConnection = bracket (connect testConnectInfo) close
test/TextRow.hs view
@@ -3,18 +3,22 @@  module TextRow where -import           Control.Applicative import           Data.Time.Calendar  (fromGregorian) import           Data.Time.LocalTime (LocalTime (..), TimeOfDay (..)) import           Database.MySQL.Base+import           QueryApi import qualified System.IO.Streams   as Stream import           Test.Tasty.HUnit import qualified Data.Vector as V -tests :: MySQLConn -> Assertion-tests c = do-    (f, is) <- query_ c "SELECT * FROM test"+-- | The columns of the @test@ table.+testColumns :: ColumnCount+testColumns = ColumnCount 30 +tests :: QueryApi -> MySQLConn -> Assertion+tests api c = do+    (f, is1) <- apiQuery_ api testColumns c "SELECT * FROM test"+     assertEqual "decode Field types" (columnType <$> f)         [ mySQLTypeLong         , mySQLTypeBit@@ -48,8 +52,8 @@         , mySQLTypeString         ] -    Just v <- Stream.read is-    assertEqual "decode NULL values" v+    Just v1 <- Stream.read is1+    assertEqual "decode NULL values" v1         [ MySQLInt32 0         , MySQLNull         , MySQLNull@@ -82,11 +86,11 @@         , MySQLNull         ] -    Stream.skipToEof is+    Stream.skipToEof is1      let bitV = 57514 -- 0b1110000010101010 -    execute_ c "UPDATE test SET \+    _ <- execute_ c "UPDATE test SET \                 \__bit        = b'1110000010101010'                    ,\                 \__tinyInt    = -128                                   ,\                 \__tinyIntU   = 255                                    ,\@@ -117,10 +121,10 @@                 \__enum       = 'foo'                                  ,\                 \__set        = 'foo,bar' WHERE __id=0" -    (_, is) <- query_ c "SELECT * FROM test"-    Just v <- Stream.read is+    (_, is2) <- apiQuery_ api testColumns c "SELECT * FROM test"+    Just v2 <- Stream.read is2 -    assertEqual "decode text protocol" v+    assertEqual "decode text protocol" v2         [ MySQLInt32 0         , MySQLBit bitV         , MySQLInt8 (-128)@@ -153,15 +157,15 @@         , MySQLText "foo,bar"         ] -    Stream.skipToEof is+    Stream.skipToEof is2 -    (_, is') <- queryVector_ c "SELECT * FROM test"-    Just v' <- Stream.read is'-    Stream.skipToEof is'+    (_, is3) <- apiQueryVector_ api testColumns c "SELECT * FROM test"+    Just v3 <- Stream.read is3+    Stream.skipToEof is3 -    assertEqual "decode text protocol(queryVector_)" v (V.toList v')+    assertEqual "decode text protocol(queryVector_)" v2 (V.toList v3) -    execute c "UPDATE test SET \+    _ <- execute c "UPDATE test SET \             \__bit        = ?     ,\             \__tinyInt    = ?     ,\             \__tinyIntU   = ?     ,\@@ -222,10 +226,10 @@                 , MySQLText "foo,bar"                 ] -    (_, is) <- query_ c "SELECT * FROM test"-    Just v <- Stream.read is+    (_, is4) <- apiQuery_ api testColumns c "SELECT * FROM test"+    Just v4 <- Stream.read is4 -    assertEqual "roundtrip text protocol" v+    assertEqual "roundtrip text protocol" v4         [ MySQLInt32 0         , MySQLBit bitV         , MySQLInt8 (-128)@@ -258,18 +262,18 @@         , MySQLText "foo,bar"         ] -    Stream.skipToEof is+    Stream.skipToEof is4  -    execute_ c "UPDATE test SET \+    _ <- execute_ c "UPDATE test SET \         \__mediumInt  = null         ,\         \__double     = null         ,\         \__text = null WHERE __id=0" -    (_, is) <- query_ c "SELECT * FROM test"-    Just v <- Stream.read is+    (_, is5) <- apiQuery_ api testColumns c "SELECT * FROM test"+    Just v5 <- Stream.read is5 -    assertEqual "decode text protocol with null" v+    assertEqual "decode text protocol with null" v5         [ MySQLInt32 0         , MySQLBit bitV         , MySQLInt8 (-128)@@ -302,18 +306,18 @@         , MySQLText "foo,bar"         ] -    Stream.skipToEof is+    Stream.skipToEof is5 -    execute c "UPDATE test SET \+    _ <- execute c "UPDATE test SET \         \__decimal  = ?         ,\         \__date     = ?         ,\         \__timestamp = ? WHERE __id=0"         [MySQLNull, MySQLNull, MySQLNull] -    (_, is) <- query_ c "SELECT * FROM test"-    Just v <- Stream.read is+    (_, is6) <- apiQuery_ api testColumns c "SELECT * FROM test"+    Just v6 <- Stream.read is6 -    assertEqual "roundtrip text protocol with null" v+    assertEqual "roundtrip text protocol with null" v6         [ MySQLInt32 0         , MySQLBit bitV         , MySQLInt8 (-128)@@ -346,47 +350,47 @@         , MySQLText "foo,bar"         ] -    Stream.skipToEof is+    Stream.skipToEof is6 -    execute_ c "UPDATE test SET \+    _ <- execute_ c "UPDATE test SET \         \__time       = '199:59:59'     ,\         \__year       = 0  WHERE __id=0" -    (_, is) <- query_ c "SELECT __time, __year FROM test"-    Just v <- Stream.read is+    (_, is7) <- apiQuery_ api (ColumnCount 2) c "SELECT __time, __year FROM test"+    Just v7 <- Stream.read is7 -    assertEqual "decode text protocol 2" v+    assertEqual "decode text protocol 2" v7             [ MySQLTime 0 (TimeOfDay 199 59 59)             , MySQLYear 0             ] -    Stream.skipToEof is+    Stream.skipToEof is7 -    execute_ c "UPDATE test SET \+    _ <- execute_ c "UPDATE test SET \         \__text       = ''     ,\         \__blob       = ''  WHERE __id=0" -    (_, is) <- query_ c "SELECT __text, __blob FROM test"-    Just v <- Stream.read is+    (_, is8) <- apiQuery_ api (ColumnCount 2) c "SELECT __text, __blob FROM test"+    Just v8 <- Stream.read is8 -    assertEqual "decode text protocol 3" v+    assertEqual "decode text protocol 3" v8             [ MySQLText ""             , MySQLBytes ""             ] -    Stream.skipToEof is+    Stream.skipToEof is8 -    execute c "UPDATE test SET \+    _ <- execute c "UPDATE test SET \         \__time       = ?     ,\         \__year       = ?  WHERE __id=0"         [ MySQLTime 0 (TimeOfDay 199 59 59), MySQLYear 0] -    (_, is) <- query_ c "SELECT __time, __year FROM test"-    Just v <- Stream.read is+    (_, is9) <- apiQuery_ api (ColumnCount 2) c "SELECT __time, __year FROM test"+    Just v9 <- Stream.read is9 -    assertEqual "roundtrip text protocol 2" v+    assertEqual "roundtrip text protocol 2" v9             [ MySQLTime 0 (TimeOfDay 199 59 59)             , MySQLYear 0             ] -    Stream.skipToEof is+    Stream.skipToEof is9
+ test/TextRowDecoding.hs view
@@ -0,0 +1,205 @@+-- | 'decodeTextRow', which the query functions decode text-protocol rows with,+-- must give the values 'getTextRow' gives, and say where a row is malformed.+module TextRowDecoding+    ( tests+      -- * Rows for other tests+    , genRow+    , genColumn+    , genField+    , encodeRow+    , columnOf+    , employeeColumns+    , employeeRow+    , utf8+    , binary+    , unsigned+    ) where++import           Data.Binary.Parser                 (parseOnly)+import           Data.Binary.Put                    (Put, putByteString,+                                                     putWord8, runPut)+import           Data.ByteString                    (ByteString)+import qualified Data.ByteString                    as B+import qualified Data.ByteString.Char8              as BC+import qualified Data.ByteString.Lazy               as L+import qualified Data.Text.Encoding                 as T+import           Data.Time.Calendar                 (Day, fromGregorian)+import           Data.Time.LocalTime                (LocalTime, TimeOfDay)+import           Data.Word                          (Word16)+import           Database.MySQL.Protocol.ColumnDef+import           Database.MySQL.Protocol.MySQLValue+import           Database.MySQL.Protocol.Packet     (putLenEncInt)+import           Test.QuickCheck+import           Test.QuickCheck.Instances          ()+import           Test.Tasty+import           Test.Tasty.HUnit                   (testCase, (@?=))+import           Test.Tasty.QuickCheck              (testProperty)++tests :: TestTree+tests = testGroup "text rows"+    [ testProperty "decodeTextRow gives the values getTextRow gives" $ checkCoverage $+        forAll genRow $ \(columns, fields) ->+            forAll (choose (0, B.length (encodeRow fields))) $ \kept ->+                cover 25 (decodes columns (encodeRow fields)) "the whole row decodes" $+                cover 25 (not (decodes columns (B.take kept (encodeRow fields))))+                    "the cut row fails" $+                conjoin+                    [ sameValues columns (encodeRow fields)+                    , sameValues columns (B.take kept (encodeRow fields))+                    ]+    , testCase "an employees row" $+        decodeTextRow (map columnKind employeeColumns) employeeRow+            @?= Right+                [ MySQLInt32 10001+                , MySQLDate (fromGregorian 1953 9 2)+                , MySQLText "Georgi"+                , MySQLText "Facello"+                , MySQLText "M"+                , MySQLDate (fromGregorian 1986 6 26)+                ]+    , testCase "NULL fields" $+        decodeTextRow (map columnKind (take 2 employeeColumns)) (B.pack [0xFB, 0xFB])+            @?= Right [MySQLNull, MySQLNull]+    , testCase "a row cut inside a field names that column and where it starts" $+        decodeTextRow (map columnKind employeeColumns) (B.take 20 employeeRow)+            @?= Left (RowError (ColumnNumber 2) 17 RowEndsEarly)+    , testCase "a field that does not lex names its column" $+        decodeTextRow (map columnKind employeeColumns) (encodeRow [Just "1", Just "abc"])+            @?= Left (RowError (ColumnNumber 1) 2+                        (RowFieldError (TextFieldUnparsable mySQLTypeDate "abc")))+    ]++-- | Both decoders succeed with the same values, or both fail.+sameValues :: [ColumnDef] -> ByteString -> Property+sameValues columns row =+    rowValues (decodeTextRow (map columnKind columns) row)+        === rowValues (parseOnly (getTextRow columns) row)++decodes :: [ColumnDef] -> ByteString -> Bool+decodes columns row = either (const False) (const True) (decodeTextRow (map columnKind columns) row)++rowValues :: Either rowError [MySQLValue] -> Maybe [MySQLValue]+rowValues = either (const Nothing) Just++-- | @emp_no INT, birth_date DATE, first_name VARCHAR, last_name VARCHAR,+-- gender ENUM, hire_date DATE@, as in the select benchmark.+employeeColumns :: [ColumnDef]+employeeColumns =+    [ columnOf mySQLTypeLong 0 utf8+    , columnOf mySQLTypeDate 0 binary+    , columnOf mySQLTypeVarString 0 utf8+    , columnOf mySQLTypeVarString 0 utf8+    , columnOf mySQLTypeString 0 utf8+    , columnOf mySQLTypeDate 0 binary+    ]++employeeRow :: ByteString+employeeRow = encodeRow+    [Just "10001", Just "1953-09-02", Just "Georgi", Just "Facello", Just "M", Just "1986-06-26"]++utf8, binary, latin1, gbk :: Word16+utf8 = 33+binary = 63+latin1 = 8+gbk = 28++unsigned :: Word16+unsigned = 32++columnOf :: FieldType -> Word16 -> Word16 -> ColumnDef+columnOf fieldType flags charSet = ColumnDef+    { columnDB        = ""+    , columnTable     = ""+    , columnOrigTable = ""+    , columnName      = ""+    , columnOrigName  = ""+    , columnCharSet   = charSet+    , columnLength    = 0+    , columnType      = fieldType+    , columnFlags     = flags+    , columnDecimals  = 0+    }++-- | A row's fields as the server sends them; 'Nothing' is the NULL marker.+encodeRow :: [Maybe ByteString] -> ByteString+encodeRow fields = L.toStrict (runPut (mapM_ putRowField fields))++putRowField :: Maybe ByteString -> Put+putRowField field = case field of+    Nothing    -> putWord8 0xFB+    Just bytes -> putLenEncInt (B.length bytes) >> putByteString bytes++genRow :: Gen ([ColumnDef], [Maybe ByteString])+genRow = do+    columnCount <- choose (0, 8)+    columns <- vectorOf columnCount genColumn+    fields <- mapM (genField . columnKind) columns+    pure (columns, fields)++-- | Every column type the text decoder knows, and JSON (0xf5), which it does not.+genColumn :: Gen ColumnDef+genColumn = columnOf+    <$> elements+        [ mySQLTypeDecimal, mySQLTypeNewDecimal, mySQLTypeTiny, mySQLTypeShort+        , mySQLTypeLong, mySQLTypeInt24, mySQLTypeLongLong, mySQLTypeFloat+        , mySQLTypeDouble, mySQLTypeNull, mySQLTypeYear, mySQLTypeTimestamp+        , mySQLTypeTimestamp2, mySQLTypeDateTime, mySQLTypeDateTime2+        , mySQLTypeDate, mySQLTypeNewDate, mySQLTypeTime, mySQLTypeTime2+        , mySQLTypeGeometry, mySQLTypeVarChar, mySQLTypeEnum, mySQLTypeSet+        , mySQLTypeTinyBlob, mySQLTypeMediumBlob, mySQLTypeLongBlob+        , mySQLTypeBlob, mySQLTypeVarString, mySQLTypeString, mySQLTypeBit+        , FieldType 0xf5+        ]+    <*> elements [0, unsigned]+    <*> elements [utf8, binary, latin1, gbk]++-- | A column of type NULL always holds the NULL marker.+genField :: ColumnKind -> Gen (Maybe ByteString)+genField column = case column of+    NullColumn          -> pure Nothing+    ValueColumn _ value -> frequency [(1, pure Nothing), (6, Just <$> genValueBytes value)]++-- | Mostly what the server sends for the column, sometimes bytes it would not,+-- such as a string that is not UTF-8 in a character set read as UTF-8.+genValueBytes :: ValueKind -> Gen ByteString+genValueBytes value = case value of+    KindDecimal     -> renderedOrArbitrary (arbitrary :: Gen Double)+    KindInt8U       -> renderedOrArbitrary (arbitrary :: Gen Integer)+    KindInt8        -> renderedOrArbitrary (arbitrary :: Gen Integer)+    KindInt16U      -> renderedOrArbitrary (arbitrary :: Gen Integer)+    KindInt16       -> renderedOrArbitrary (arbitrary :: Gen Integer)+    KindInt32U      -> renderedOrArbitrary (arbitrary :: Gen Integer)+    KindInt32       -> renderedOrArbitrary (arbitrary :: Gen Integer)+    KindInt64U      -> renderedOrArbitrary (arbitrary :: Gen Integer)+    KindInt64       -> renderedOrArbitrary (arbitrary :: Gen Integer)+    KindFloat       -> renderedOrArbitrary (arbitrary :: Gen Double)+    KindDouble      -> renderedOrArbitrary (arbitrary :: Gen Double)+    KindYear        -> renderedOrArbitrary (arbitrary :: Gen Integer)+    KindTimeStamp   -> renderedOrArbitrary (arbitrary :: Gen LocalTime)+    KindDateTime    -> renderedOrArbitrary (arbitrary :: Gen LocalTime)+    KindDate        -> renderedOrArbitrary (arbitrary :: Gen Day)+    KindTime        -> oneof [genTime, renderedOrArbitrary (arbitrary :: Gen TimeOfDay)]+    KindGeometry    -> arbitrary+    -- Valid UTF-8 only: invalid bytes raise when the value is used, while the+    -- typed decoders refuse the row, which ResultCharsets and DirectDecoding test.+    KindText _      -> T.encodeUtf8 <$> arbitrary+    KindLatin1Text  -> arbitrary+    KindBytes       -> frequency [(9, arbitrary), (1, genLongBytes)]+    KindBit         -> B.pack <$> (choose (1, 10) >>= vector)+    KindUnsupported -> arbitrary++renderedOrArbitrary :: Show a => Gen a -> Gen ByteString+renderedOrArbitrary gen = frequency [(4, BC.pack . show <$> gen), (1, arbitrary)]++-- | A TIME up to MySQL's 838 hours, sometimes negative.+genTime :: Gen ByteString+genTime = do+    sign <- elements ["", "-"]+    hours <- choose (0, 838 :: Int)+    minutes <- choose (0, 59 :: Int)+    seconds <- choose (0, 59 :: Int)+    pure (BC.pack (sign ++ show hours ++ ":" ++ show minutes ++ ":" ++ show seconds))++-- | Long enough for the 2- and 3-byte length prefixes.+genLongBytes :: Gen ByteString+genLongBytes = B.replicate <$> choose (251, 70000) <*> arbitrary
test/TextRowNew.hs view
@@ -3,17 +3,21 @@  module TextRowNew where -import           Control.Applicative import           Data.Time.Calendar  (fromGregorian) import           Data.Time.LocalTime (LocalTime (..), TimeOfDay (..)) import           Database.MySQL.Base+import           QueryApi import qualified System.IO.Streams   as Stream import           Test.Tasty.HUnit -tests :: MySQLConn -> Assertion-tests c = do-    (f, is) <- query_ c "SELECT * FROM test_new"+-- | The columns of the @test_new@ table.+testNewColumns :: ColumnCount+testNewColumns = ColumnCount 4 +tests :: QueryApi -> MySQLConn -> Assertion+tests api c = do+    (f, is5) <- apiQuery_ api testNewColumns c "SELECT * FROM test_new"+     assertEqual "decode Field types" (columnType <$> f)         [ mySQLTypeLong         , mySQLTypeDateTime@@ -21,51 +25,51 @@         , mySQLTypeTime         ] -    Just v <- Stream.read is-    assertEqual "decode NULL values" v+    Just v1 <- Stream.read is5+    assertEqual "decode NULL values" v1         [ MySQLInt32 0         , MySQLNull         , MySQLNull         , MySQLNull         ] -    Stream.skipToEof is+    Stream.skipToEof is5 -    execute_ c "UPDATE test_new SET \+    _ <- execute_ c "UPDATE test_new SET \                 \__datetime   = '2016-08-08 17:25:59.12'                  ,\                 \__timestamp  = '2016-08-08 17:25:59.1234'                ,\                 \__time       = '-199:59:59.123456' WHERE __id=0" -    (_, is) <- query_ c "SELECT * FROM test_new"-    Just v <- Stream.read is+    (_, is4) <- apiQuery_ api testNewColumns c "SELECT * FROM test_new"+    Just v2 <- Stream.read is4 -    assertEqual "decode text protocol" v+    assertEqual "decode text protocol" v2         [ MySQLInt32 0         , MySQLDateTime (LocalTime (fromGregorian 2016 08 08) (TimeOfDay 17 25 59.12))         , MySQLTimeStamp (LocalTime (fromGregorian 2016 08 08) (TimeOfDay 17 25 59.1234))         , MySQLTime 1 (TimeOfDay 199 59 59.123456)         ] -    Stream.skipToEof is+    Stream.skipToEof is4 -    execute_ c "UPDATE test_new SET \+    _ <- execute_ c "UPDATE test_new SET \                 \__datetime   = '2016-08-08 17:25:59.1'                  ,\                 \__timestamp  = '2016-08-08 17:25:59.12'                ,\                 \__time       = '199:59:59.1234' WHERE __id=0" -    (_, is) <- query_ c "SELECT * FROM test_new"-    Just v <- Stream.read is+    (_, is0) <- apiQuery_ api testNewColumns c "SELECT * FROM test_new"+    Just v3 <- Stream.read is0 -    assertEqual "decode text protocol 2" v+    assertEqual "decode text protocol 2" v3         [ MySQLInt32 0         , MySQLDateTime (LocalTime (fromGregorian 2016 08 08) (TimeOfDay 17 25 59.1))         , MySQLTimeStamp (LocalTime (fromGregorian 2016 08 08) (TimeOfDay 17 25 59.12))         , MySQLTime 0 (TimeOfDay 199 59 59.1234)         ] -    Stream.skipToEof is+    Stream.skipToEof is0 -    execute c "UPDATE test_new SET \+    _ <- execute c "UPDATE test_new SET \             \__datetime   = ?     ,\             \__timestamp  = ?     ,\             \__time       = ?  WHERE __id=0"@@ -75,19 +79,19 @@                 ]  -    (_, is) <- query_ c "SELECT * FROM test_new"-    Just v <- Stream.read is+    (_, is1) <- apiQuery_ api testNewColumns c "SELECT * FROM test_new"+    Just v4 <- Stream.read is1 -    assertEqual "roundtrip text protocol" v+    assertEqual "roundtrip text protocol" v4         [ MySQLInt32 0         , MySQLDateTime (LocalTime (fromGregorian 2016 08 08) (TimeOfDay 17 25 59.12))         , MySQLTimeStamp (LocalTime (fromGregorian 2016 08 08) (TimeOfDay 17 25 59.1234))         , MySQLTime 1 (TimeOfDay 199 59 59.123456)         ] -    Stream.skipToEof is+    Stream.skipToEof is1 -    execute c "UPDATE test_new SET \+    _ <- execute c "UPDATE test_new SET \             \__datetime   = ?     ,\             \__timestamp  = ?     ,\             \__time       = ?  WHERE __id=0"@@ -96,17 +100,17 @@                 , MySQLTime 0 (TimeOfDay 199 59 59.1234)                 ] -    (_, is) <- query_ c "SELECT * FROM test_new"-    Just v <- Stream.read is+    (_, is2) <- apiQuery_ api testNewColumns c "SELECT * FROM test_new"+    Just v5 <- Stream.read is2 -    assertEqual "roundtrip text protocol 2" v+    assertEqual "roundtrip text protocol 2" v5         [ MySQLInt32 0         , MySQLDateTime (LocalTime (fromGregorian 2016 08 08) (TimeOfDay 17 25 59.1))         , MySQLTimeStamp (LocalTime (fromGregorian 2016 08 08) (TimeOfDay 17 25 59.12))         , MySQLTime 0 (TimeOfDay 199 59 59.1234)         ] -    Stream.skipToEof is+    Stream.skipToEof is2      let row0 =             [ MySQLInt32 0@@ -120,13 +124,13 @@             , MySQLTimeStamp (LocalTime (fromGregorian 2016 08 09) (TimeOfDay 18 25 59.12))             , MySQLTime 0 (TimeOfDay 299 59 59.1234)             ]-    execute c "UPDATE test_new SET \+    _ <- execute c "UPDATE test_new SET \             \__id         = ?     ,\             \__datetime   = ?     ,\             \__timestamp  = ?     ,\             \__time       = ?  WHERE __id=0"             row0-    execute c "INSERT INTO test_new VALUES(\+    _ <- execute c "INSERT INTO test_new VALUES(\             \?,\             \?,\             \?,\@@ -134,13 +138,13 @@             \)"             row1 -    (_, is) <- query c "SELECT * FROM test_new WHERE __id IN (?) ORDER BY __id" [Many [MySQLInt32 0, MySQLInt32 1]]-    Just v0 <- Stream.read is-    Just v1 <- Stream.read is+    (_, is3) <- apiQuery api testNewColumns c "SELECT * FROM test_new WHERE __id IN (?) ORDER BY __id" [Many [MySQLInt32 0, MySQLInt32 1]]+    Just v6 <- Stream.read is3+    Just v7 <- Stream.read is3 -    assertEqual "select list of ids" [v0, v1] [row0, row1]+    assertEqual "select list of ids" [v6, v7] [row0, row1] -    Stream.skipToEof is-    execute_ c "DELETE FROM test_new where __id=1"+    Stream.skipToEof is3+    _ <- execute_ c "DELETE FROM test_new where __id=1"      return ()
+ test/TypedValue.hs view
@@ -0,0 +1,38 @@+-- | A column read with the typed decoder of "Database.MySQL.Decoder" for its+-- type, then put back into the 'MySQLValue' constructor the old API returns,+-- so that tests written against the old API hold for the typed decoders too.+module TypedValue (typedDecoder) where++import           Data.Maybe                         (fromMaybe)+import qualified Database.MySQL.Decoder             as Decode+import           Database.MySQL.Protocol.MySQLValue++-- | The typed decoder for a column's type. TIME has none here, as+-- 'Decode.timeOfDay' takes only non-negative times and the tests read+-- negative ones; YEAR, BIT and GEOMETRY have no typed decoder at all.+typedDecoder :: ColumnKind -> Decode.FieldDecoder MySQLValue+typedDecoder column = fromMaybe MySQLNull <$> Decode.nullable (case column of+    NullColumn -> Decode.mysqlValue+    ValueColumn _ kind -> case kind of+        KindDecimal     -> MySQLDecimal <$> Decode.scientific+        KindInt8U       -> MySQLInt8U <$> Decode.word8+        KindInt8        -> MySQLInt8 <$> Decode.int8+        KindInt16U      -> MySQLInt16U <$> Decode.word16+        KindInt16       -> MySQLInt16 <$> Decode.int16+        KindInt32U      -> MySQLInt32U <$> Decode.word32+        KindInt32       -> MySQLInt32 <$> Decode.int32+        KindInt64U      -> MySQLInt64U <$> Decode.word64+        KindInt64       -> MySQLInt64 <$> Decode.int64+        KindFloat       -> MySQLFloat <$> Decode.float+        KindDouble      -> MySQLDouble <$> Decode.double+        KindYear        -> Decode.mysqlValue+        KindTimeStamp   -> MySQLTimeStamp <$> Decode.localTime+        KindDateTime    -> MySQLDateTime <$> Decode.localTime+        KindDate        -> MySQLDate <$> Decode.day+        KindTime        -> Decode.mysqlValue+        KindGeometry    -> Decode.mysqlValue+        KindText _      -> MySQLText <$> Decode.text+        KindLatin1Text  -> MySQLText <$> Decode.text+        KindBytes       -> MySQLBytes <$> Decode.bytes+        KindBit         -> Decode.mysqlValue+        KindUnsupported -> Decode.mysqlValue)
+ test/UnixSocket.hs view
@@ -0,0 +1,94 @@+{-# LANGUAGE ScopedTypeVariables #-}++module UnixSocket (tests, findSocketPath) where++import qualified Data.ByteString       as B+import           Database.MySQL.Base+import           QueryApi+import qualified System.IO.Streams     as Stream+import           System.Directory      (doesFileExist)+import           System.Environment    (lookupEnv)+import           Test.Tasty+import           Test.Tasty.HUnit++-- | Common Unix socket paths for MySQL/MariaDB.+defaultSocketPaths :: [FilePath]+defaultSocketPaths =+    [ "/tmp/mysql.sock"                    -- macOS Homebrew+    , "/var/run/mysqld/mysqld.sock"        -- Debian/Ubuntu+    , "/run/mysqld/mysqld.sock"            -- NixOS, Arch+    , "/var/lib/mysql/mysql.sock"          -- RHEL/CentOS+    , "/tmp/mysqld.sock"                   -- some macOS installs+    ]++-- | Find the MySQL Unix socket path.  Checks the @MYSQL_UNIX_SOCKET@+-- environment variable first, then probes common default locations.+findSocketPath :: IO (Maybe FilePath)+findSocketPath = do+    envPath <- lookupEnv "MYSQL_UNIX_SOCKET"+    case envPath of+        Just p  -> do+            exists <- doesFileExist p+            return $ if exists then Just p else Nothing+        Nothing -> firstExisting defaultSocketPaths+  where+    firstExisting []     = return Nothing+    firstExisting (p:ps) = do+        exists <- doesFileExist p+        if exists then return (Just p) else firstExisting ps++tests :: QueryApi -> FilePath -> TestTree+tests api socketPath = testGroup "unix-socket"+    [ testCaseSteps "connectUnixSocket: SELECT 1" $ \step -> do+        step "connecting via unix socket..."+        c <- connectUnixSocket socketPath defaultConnectInfo+            { ciUser     = "testMySQLHaskell"+            , ciDatabase = "testMySQLHaskell"+            }++        step "executing SELECT 1..."+        (_, is) <- apiQuery_ api (ColumnCount 1) c "SELECT 1"+        Just row <- Stream.read is+        assertBool "SELECT 1 returns 1"+            (row == [MySQLInt32 1] || row == [MySQLInt64 1])+        Stream.skipToEof is++        close c++    , testCaseSteps "connectUnixSocketDetail: returns greeting" $ \step -> do+        step "connecting via unix socket with detail..."+        (greet, c) <- connectUnixSocketDetail socketPath defaultConnectInfo+            { ciUser     = "testMySQLHaskell"+            , ciDatabase = "testMySQLHaskell"+            }++        step "checking greeting version..."+        let ver = greetingVersion greet+        assertBool "greeting version is not empty" (not $ B.null ver)++        step "executing query to verify connection..."+        (_, is) <- apiQuery_ api (ColumnCount 1) c "SELECT 1 + 1"+        Just row <- Stream.read is+        assertBool "SELECT 1+1 returns 2"+            (row == [MySQLInt32 2] || row == [MySQLInt64 2])+        Stream.skipToEof is++        close c++    , testCaseSteps "unix socket: prepared statement roundtrip" $ \step -> do+        step "connecting via unix socket..."+        c <- connectUnixSocket socketPath defaultConnectInfo+            { ciUser     = "testMySQLHaskell"+            , ciDatabase = "testMySQLHaskell"+            }++        step "executing prepared statement..."+        stmt <- prepareStmt c "SELECT ? + 1"+        (_, is) <- apiQueryStmt api (ColumnCount 1) c stmt [MySQLInt32 41]+        Just row <- Stream.read is+        assertBool "41 + 1 = 42"+            (row == [MySQLInt32 42] || row == [MySQLInt64 42])+        Stream.skipToEof is++        close c+    ]
+ test/UnsignedInsertId.hs view
@@ -0,0 +1,29 @@+-- | Insert IDs above Int's range: BIGINT UNSIGNED AUTO_INCREMENT goes up to+-- 2^64 - 1, and OK packets carry it unsigned.+module UnsignedInsertId (tests) where++import           Database.MySQL.Base+import           Test.Tasty+import           Test.Tasty.HUnit+import           TestUser            (withTestConnection)++tests :: TestTree+tests = testGroup "insert IDs above 2^63"+    [ testCase "execute_" $ withUnsignedIds $ \c -> do+        ok <- execute_ c "INSERT INTO unsigned_ids (v) VALUES (1)"+        assertEqual "last insert ID" firstId (toInteger (okLastInsertID ok))+    , testCase "executeStmt" $ withUnsignedIds $ \c -> do+        stmt <- prepareStmt c "INSERT INTO unsigned_ids (v) VALUES (?)"+        ok <- executeStmt c stmt [MySQLInt32 1]+        assertEqual "last insert ID" firstId (toInteger (okLastInsertID ok))+    ]++firstId :: Integer+firstId = 18446744073709551000++withUnsignedIds :: (MySQLConn -> IO ()) -> Assertion+withUnsignedIds run = withTestConnection $ \c -> do+    _ <- execute_ c "CREATE TEMPORARY TABLE unsigned_ids \+                    \(id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, v INT) \+                    \AUTO_INCREMENT = 18446744073709551000"+    run c
+ test/Word24.hs view
@@ -0,0 +1,447 @@+module Word24(tests) where++import Prelude as P++import Test.Tasty+import           Test.Tasty.QuickCheck    (testProperty)++import Test.QuickCheck hiding ((.&.))++import Orphans()+import Data.Int+import Data.Int.Int24+import Data.Word+import Data.Word.Word24+import Data.Bits+import Foreign.Storable+import GHC.Real++prop_enum3 :: Int -> Bool+prop_addIdent :: Word24 -> Bool+prop_multIdent :: Word24 -> Bool+prop_unsigned :: Word24 -> Bool+prop_smaller :: Word16 -> Bool+prop_show :: Word16 -> Bool+prop_read :: Word24 -> Bool+prop_adder :: Word16 -> Word16 -> Property+prop_negate :: Word24 -> Bool+prop_abs :: Word24 -> Bool+prop_signum :: Word24 -> Bool+prop_real :: Word24 -> Bool+prop_enum1 :: Word24 -> Property+prop_enum2 :: Word24 -> Property+prop_enum4 :: Word24 -> Property+prop_enum5 :: Word24 -> Word8 -> Bool+prop_enum6 :: Word24 -> Word24 -> Bool+prop_quot :: Word24 -> Word24 -> Property+prop_rem :: Word24 -> Word24 -> Property+prop_div :: Word24 -> Word24 -> Property+prop_mod :: Word24 -> Word24 -> Property+prop_quotrem :: Word24 -> Word24 -> Property+prop_divmod :: Word24 -> Word24 -> Property+prop_and :: Word16 -> Word16 -> Bool+prop_or :: Word16 -> Word16 -> Bool+prop_xor :: Word16 -> Word16 -> Bool+prop_xor_ident :: Word24 -> Word24 -> Bool+prop_shiftL :: Word24 -> Bool+prop_shiftR :: Word24 -> Property+prop_shiftR2 :: Word24 -> Int -> Property+prop_shiftL_ident :: Word24 -> Bool+prop_rotate :: Word24 -> Int -> Bool+prop_comp :: Word24 -> Bool+prop_byteSwap :: Word24 -> Bool+prop_clz :: Word24 -> Bool+prop_ctz :: Word24 -> Bool+prop_bit_ident :: Bits a => a -> NonNegative Int -> Bool+prop_sizeOf :: Word24 -> Bool+prop_align :: Word24 -> Bool+prop_addIdentI :: Int24 -> Bool+prop_multIdentI :: Int24 -> Bool+prop_smallerI :: Int16 -> Bool+prop_showI :: Int16 -> Bool+prop_readI :: Int24 -> Bool+prop_adderI :: Int16 -> Int16 -> Bool+prop_negateI :: Int24 -> Bool+prop_absI :: Int24 -> Bool+prop_signumI :: Int24 -> Bool+prop_realI :: Int24 -> Bool+prop_enum1I :: Int24 -> Property+prop_enum2I :: Int24 -> Property+prop_enum3I :: Int -> Bool+prop_enum4I :: Int24 -> Property+prop_enum5I :: Int24 -> Word8 -> Bool+prop_enum6I :: Int24 -> Int24 -> Bool+-- | Int24 division throws for a zero divisor and, like 'Int' quot, for+-- minBound divided by -1 (Data.Int.Int24 raises overflowError there).+int24DivisionDefined :: Int24 -> Int24 -> Bool+int24DivisionDefined a b = b /= 0 && not (a == minBound && b == (-1))++prop_quotI :: Int24 -> Int24 -> Property+prop_remI :: Int24 -> Int24 -> Property+prop_divI :: Int24 -> Int24 -> Property+prop_modI :: Int24 -> Int24 -> Property++-- ----------------------------------------+-- Word24 Properties++prop_addIdent a = a - a == 0++prop_multIdent a = a * 1 == a++prop_unsigned a = a >= 0++prop_smaller a = (fromIntegral ((fromIntegral a) :: Word24) :: Word16) == a++prop_show a = show a == show (fromIntegral a :: Word24)++prop_read a = a == (read . show) a++prop_adder a b = (a < ub) && (b < ub) ==>+                 fromIntegral (a + b) ==+                 ((fromIntegral a + fromIntegral b) :: Word24)+     where+        ub :: Word16+        ub = maxBound `div` 2++prop_negate a = a == negate (negate a)++prop_abs a = a == abs a++prop_signum a = if a == 0 then signum a == a else signum a == 1++prop_real a = let r = toRational a in numerator r == fromIntegral a++-- Word24 Enum properties+prop_enum1 a = a < maxBound ==> succ a == a + 1++prop_enum2 a = a > minBound ==> pred a == a - 1++prop_enum3 a = let a' = abs a in+               toEnum a' == fromIntegral @_ @Integer a'++prop_enum4 a = a < maxBound ==> take 2 (enumFrom a) == [a, a+1]++prop_enum5 a b = let b' = fromIntegral b in+                 enumFromTo a (a + b') ==+                 map fromIntegral (enumFromTo (fromIntegral a :: Integer)+                                             (fromIntegral (a + b') :: Integer))++prop_enum6 a b = take 2 (enumFromThen a b) == [a,b]++-- Word24 Integral properties+prop_quot a b = b /= 0 ==>+  quot a b == fromIntegral (quot (fromIntegral a :: Word32) (fromIntegral b :: Word32))++prop_rem a b = b /= 0 ==>+  rem a b == fromIntegral (rem (fromIntegral a :: Word32) (fromIntegral b :: Word32))++prop_div a b = b /= 0 ==>+  div a b == fromIntegral (div (fromIntegral a :: Word32) (fromIntegral b :: Word32))++prop_mod a b = b /= 0 ==>+  mod a b == fromIntegral (mod (fromIntegral a :: Word32) (fromIntegral b :: Word32))++prop_quotrem a b = b /= 0 ==> let (j, k) = quotRem a b in+  a == (b * j) + k++prop_divmod a b = b /= 0 ==>+  divMod a b == (div a b, mod a b)++-- binary Word properties+prop_and a b = (a .&. b) == fromIntegral ( (fromIntegral a :: Word24) .&. (fromIntegral b :: Word24))++prop_or a b = (a .|. b) == fromIntegral ( (fromIntegral a :: Word24) .|. (fromIntegral b :: Word24))++prop_xor a b = (a `xor` b) == fromIntegral ( (fromIntegral a :: Word24) `xor` (fromIntegral b :: Word24))++prop_xor_ident a b = (a `xor` b) `xor` b == a++prop_shiftL a = a `shiftL` 1 == a * 2++prop_shiftR a = a < maxBound `div` 2 ==>+  (a * 2) `shift` (-1) == a++prop_shiftR2 a n = n >= 0 ==> a `shiftR` n == a `shift` (negate n)++prop_shiftL_ident a = a `shiftL` 0 == a++prop_rotate a b = (a `rotate` b) `rotate` (negate b) == a++prop_comp a = complement (complement a) == a++prop_byteSwap a = byteSwap24 (byteSwap24 a) == a++prop_clz a = countLeadingZeros a == countLeadingZeros' a+  where+    countLeadingZeros' :: Word24 -> Int+    countLeadingZeros' x = (w-1) - go (w-1)+      where+        go i | i < 0       = i -- no bit set+             | testBit x i = i+             | otherwise   = go (i-1)++        w = finiteBitSize x++prop_ctz a = countTrailingZeros a == countTrailingZeros' a+  where+    countTrailingZeros' :: Word24 -> Int+    countTrailingZeros' x = go 0+      where+        go i | i >= w      = i+             | testBit x i = i+             | otherwise   = go (i+1)+        w = finiteBitSize x++prop_bit_ident q (NonNegative j) = testBit (bit j `asTypeOf` q) j == (j < 24)++prop_popCount :: (Integral a1, Num a2, FiniteBits a1,+                        FiniteBits a2) =>+                       a1 -> a2 -> a1 -> Bool+prop_popCount s t a = if a >= 0+  then popCount (a `asTypeOf` s) == popCount (fromIntegral a `asTypeOf` t)+  else+    finiteBitSize s - popCount (a `asTypeOf` s) == finiteBitSize t - popCount (fromIntegral a `asTypeOf` t)++-- Word Storable properties+prop_sizeOf a = sizeOf a == 3++prop_align a = alignment a == 3++++-- ----------------------------------------+-- Int24 Properties++prop_addIdentI a = a - a == 0++prop_multIdentI a = a * 1 == a++prop_smallerI a = (fromIntegral ((fromIntegral a) :: Int24) :: Int16) == a++prop_showI a = show a == show (fromIntegral a :: Int24)++prop_readI a = a == (read . show) a++prop_adderI a b = ((fromIntegral a + fromIntegral b) :: Int) ==+                  fromIntegral ((fromIntegral a + fromIntegral b) :: Int24)++prop_negateI a = a == negate (negate a)++prop_absI a = if a >= 0 then a == abs a else a == negate (abs a)++prop_signumI a = signum a == fromIntegral (signum (fromIntegral a :: Int))++prop_realI a = let r = toRational a in numerator r == fromIntegral a++-- Int24 Enum Properties+prop_enum1I a = a < maxBound ==> succ a == a + 1++prop_enum2I a = a > minBound ==> pred a == a - 1++prop_enum3I a = let a' = abs a in+               toEnum a' == fromIntegral @_ @Integer a'++prop_enum4I a = a < maxBound ==> take 2 (enumFrom a) == [a, a+1]++prop_enum5I a b = let b' = fromIntegral b in+                 enumFromTo a (a + b') ==+                 map fromIntegral (enumFromTo (fromIntegral a :: Integer)+                                             (fromIntegral (a + b') :: Integer))++prop_enum6I a b = take 2 (enumFromThen a b) == [a,b]++-- Int24 Integral properties+prop_quotI a b = int24DivisionDefined a b ==>+  quot a b == fromIntegral (quot (fromIntegral a :: Int32) (fromIntegral b :: Int32))++prop_remI a b = int24DivisionDefined a b ==>+  rem a b == fromIntegral (rem (fromIntegral a :: Int32) (fromIntegral b :: Int32))++prop_divI a b = int24DivisionDefined a b ==>+  div a b == fromIntegral (div (fromIntegral a :: Int32) (fromIntegral b :: Int32))++prop_modI a b = int24DivisionDefined a b ==>+  mod a b == fromIntegral (mod (fromIntegral a :: Int32) (fromIntegral b :: Int32))++prop_quotremI :: Int24 -> Int24 -> Property+prop_quotremI a b = int24DivisionDefined a b ==> let (j, k) = quotRem a b in+  a == (b * j) + k++prop_divmodI :: Int24 -> Int24 -> Property+prop_divmodI a b = int24DivisionDefined a b ==>+  divMod a b == (div a b, mod a b)+++-- binary Int properties+prop_andI :: Int16 -> Int16 -> Bool+prop_andI a b = (a .&. b) == fromIntegral ( (fromIntegral a :: Int24) .&. (fromIntegral b :: Int24))++prop_orI :: Int16 -> Int16 -> Bool+prop_orI a b = (a .|. b) == fromIntegral ( (fromIntegral a :: Int24) .|. (fromIntegral b :: Int24))++prop_xorI :: Int16 -> Int16 -> Bool+prop_xorI a b = (a `xor` b) == fromIntegral ( (fromIntegral a :: Int24) `xor` (fromIntegral b :: Int24))++prop_xor_identI :: Int24 -> Int24 -> Bool+prop_xor_identI a b = (a `xor` b) `xor` b == a++prop_shiftLI :: Int24 -> Bool+prop_shiftLI a = a `shiftL` 1 == a * 2++prop_shiftL_identI :: Int24 -> Bool+prop_shiftL_identI a = a `shiftL` 0 == a++prop_shiftRI :: Int24 -> Property+prop_shiftRI a = (a < maxBound `div` 2) && (a > minBound `div` 2) ==>+  (a * 2) `shift` (-1) == a++prop_shiftR2I :: Int24 -> Int -> Property+prop_shiftR2I a n = n >= 0 ==> a `shiftR` n == a `shift` (negate n)++prop_rotateI :: Int24 -> Int -> Bool+prop_rotateI a b = (a `rotate` b) `rotate` (negate b) == a++prop_compI :: Int24 -> Bool+prop_compI a = complement (complement a) == a++prop_clzI :: Int24 -> Bool+prop_clzI a = countLeadingZeros a == countLeadingZeros' a+  where+    countLeadingZeros' :: Int24 -> Int+    countLeadingZeros' x = (w-1) - go (w-1)+      where+        go i | i < 0       = i -- no bit set+             | testBit x i = i+             | otherwise   = go (i-1)++        w = finiteBitSize x++prop_ctzI :: Int24 -> Bool+prop_ctzI a = countTrailingZeros a == countTrailingZeros' a+  where+    countTrailingZeros' :: Int24 -> Int+    countTrailingZeros' x = go 0+      where+        go i | i >= w      = i+             | testBit x i = i+             | otherwise   = go (i+1)+        w = finiteBitSize x++-- Int Storable properties+prop_sizeOfI :: Int24 -> Bool+prop_sizeOfI a = sizeOf a == 3++prop_alignI :: Int24 -> Bool+prop_alignI a = alignment a == 3+++-- ----------------------------------------+-- tests+tests :: [TestTree]+tests = [+ testGroup "Word24"+  [ testGroup "basic" [+    testProperty "add. identity" prop_addIdent+    ,testProperty "mult. identity" prop_multIdent+    ,testProperty "unsigned" prop_unsigned+    ,testProperty "Word16/Word24 conversion" prop_smaller+    ,testProperty "Show" prop_show+    ,testProperty "Read" prop_read+    ,testProperty "addition" prop_adder+    ,testProperty "negate identity" prop_negate+    ,testProperty "absolute value" prop_abs+    ,testProperty "signum" prop_signum+    ,testProperty "Real identity" prop_real+    ]+  ,testGroup "Integral instance" [+    testProperty  "quot" prop_quot+    ,testProperty "rem" prop_rem+    ,testProperty "div" prop_div+    ,testProperty "mod" prop_mod+    ,testProperty "quotRem" prop_quotrem+    ,testProperty "divmod" prop_divmod+    ]+  ,testGroup "Enum instance" [+    testProperty "enum succ" prop_enum1+    ,testProperty "enum pred" prop_enum2+    ,testProperty "toEnum" prop_enum3+    ,testProperty "enumFrom " prop_enum4+    ,testProperty "enumFromTo" prop_enum5+    ,testProperty "enumFromThen" prop_enum6+    ]+  ,testGroup "Bits instance" [+    testProperty "binary and" prop_and+    ,testProperty "binary or" prop_or+    ,testProperty "binary xor" prop_xor+    ,testProperty "binary xor identity" prop_xor_ident+    ,testProperty "binary shiftL" prop_shiftL+    ,testProperty "binary shiftL identity" prop_shiftL_ident+    ,testProperty "binary shift right" prop_shiftR+    ,testProperty "binary shiftR" prop_shiftR2+    ,testProperty "binary rotate" prop_rotate+    ,testProperty "binary complement" prop_comp+    ,testProperty "binary byteSwap24" prop_byteSwap+    ,testProperty "binary countLeadingZeros" prop_clz+    ,testProperty "binary countTrailingZeros" prop_ctz+    ,testProperty "binary countTrailingZeros 0" (prop_ctz (0::Word24))+    ,testProperty "bit/testBit" (prop_bit_ident (0::Word24))+    ,testProperty "popCount"    (prop_popCount (0::Word24) (0::Word))+    ]+  ,testGroup "Storable instance" [+    testProperty  "sizeOf Word24" prop_sizeOf+    ,testProperty "aligntment" prop_align+    ]+  ]+ ,testGroup "Int24"+  [testGroup "basic" [+    testProperty "add. identity" prop_addIdentI+    ,testProperty "mult. identity" prop_multIdentI+    ,testProperty "Int16/Int24 conversion" prop_smallerI+    ,testProperty "Show" prop_showI+    ,testProperty "Read" prop_readI+    ,testProperty "addition" prop_adderI+    ,testProperty "negate identity" prop_negateI+    ,testProperty "absolute value" prop_absI+    ,testProperty "signum" prop_signumI+    ,testProperty "Real identity" prop_realI+    ]+  ,testGroup "Integral instance" [+    testProperty  "quot" prop_quotI+    ,testProperty "rem" prop_remI+    ,testProperty "div" prop_divI+    ,testProperty "mod" prop_modI+    ,testProperty "quotRem" prop_quotremI+    ,testProperty "divmod" prop_divmodI+    ]+  ,testGroup "Enum instance" [+    testProperty "enum succ" prop_enum1I+    ,testProperty "enum pred" prop_enum2I+    ,testProperty "toEnum" prop_enum3I+    ,testProperty "enumFrom " prop_enum4I+    ,testProperty "enumFromTo" prop_enum5I+    ,testProperty "enumFromThen" prop_enum6I+    ]+  ,testGroup "Bits instance" [+    testProperty "binary and" prop_andI+    ,testProperty "binary or" prop_orI+    ,testProperty "binary xor" prop_xorI+    ,testProperty "binary xor identity" prop_xor_identI+    ,testProperty "binary shiftL" prop_shiftLI+    ,testProperty "binary shiftL identity" prop_shiftL_identI+    ,testProperty "binary shift right" prop_shiftRI+    ,testProperty "binary shiftR" prop_shiftR2I+    ,testProperty "binary rotate" prop_rotateI+    ,testProperty "binary complement" prop_compI+    ,testProperty "binary countLeadingZeros" prop_clzI+    ,testProperty "binary countTrailingZeros" prop_ctzI+    ,testProperty "binary countTrailingZeros 0" (prop_ctzI (0::Int24))+    ,testProperty "bit/testBit" (prop_bit_ident (0::Int24))+    ,testProperty "popCount"    (prop_popCount (0::Int24) (0::Int))+    ]+  ,testGroup "Storable instance" [+    testProperty  "sizeOf Int24" prop_sizeOfI+    ,testProperty "aligntment" prop_alignI+    ]+  ]+ ]+
+ test/ZeroDateRoundtrip.hs view
@@ -0,0 +1,73 @@+-- | Dates a 'Day' cannot hold, read and written back through both protocols.+-- They used to come back clamped to a real date (0000-00-00 as 0000-01-01),+-- and that date was what got written back.+module ZeroDateRoundtrip (tests, typedDecoderTests) where++import           Control.Exception   (try)+import           Control.Monad       (forM_, void)+import qualified Data.ByteString.Lazy as L+import           Database.MySQL.Base+import qualified Database.MySQL.Decoder as Decode+import           QueryApi+import qualified System.IO.Streams   as Stream+import           Test.Tasty+import           Test.Tasty.HUnit+import           TestUser            (withTestConnection)++tests :: QueryApi -> TestTree+tests api = testCase ("zero, partial and invalid dates survive a round trip (" ++ apiName api ++ ")") $+    withTestConnection $ \c -> do+        createZeroDates c++        (_, textRows) <- apiQuery_ api (ColumnCount 3) c "SELECT d, dt, ts FROM zero_dates ORDER BY id"+        textValues <- Stream.toList textRows+        forM_ (zip [11 ..] textValues) $ \(newId, values) ->+            execute c "INSERT INTO zero_dates VALUES (?, ?, ?, ?)" (MySQLInt32 newId : values)++        select <- prepareStmt c "SELECT d, dt, ts FROM zero_dates WHERE id < 10 ORDER BY id"+        (_, binaryRows) <- apiQueryStmt api (ColumnCount 3) c select []+        binaryValues <- Stream.toList binaryRows+        insert <- prepareStmt c "INSERT INTO zero_dates VALUES (?, ?, ?, ?)"+        forM_ (zip [21 ..] binaryValues) $ \(newId, values) ->+            executeStmt c insert (MySQLInt32 newId : values)++        original <- storedAsText c "id < 10"+        assertEqual "values stored" 3 (length original)+        textCopies <- storedAsText c "id BETWEEN 11 AND 19"+        assertEqual "written back through the text protocol" original textCopies+        binaryCopies <- storedAsText c "id BETWEEN 21 AND 29"+        assertEqual "written back through the binary protocol" original binaryCopies++-- | 'Decode.day' and 'Decode.localTime' have no value for such a date, so they+-- name it in the error instead.+typedDecoderTests :: TestTree+typedDecoderTests = testCase "the typed date decoders refuse a date a Day cannot hold" $+    withTestConnection $ \c -> do+        createZeroDates c+        textDay <- try (queryRows_ (Decode.field Decode.day) c "SELECT d FROM zero_dates WHERE id = 2"+                        >>= Stream.toList . snd)+        assertEqual "text DATE" (Left (unrepresentable (RawDate 2024 0 0))) (void textDay)+        select <- prepareStmt c "SELECT dt FROM zero_dates WHERE id = 2"+        binaryTime <- try (queryStmtRows (Decode.field Decode.localTime) c select []+                           >>= Stream.toList . snd)+        assertEqual "binary DATETIME" (Left (unrepresentable (RawDate 2024 5 0))) (void binaryTime)++unrepresentable :: RawDate -> Decode.FieldError+unrepresentable = Decode.FieldError (Decode.ColumnNumber 0) . Decode.FieldUnrepresentableDate++createZeroDates :: MySQLConn -> IO ()+createZeroDates c = do+    void (execute_ c "SET SESSION sql_mode = 'ALLOW_INVALID_DATES'")+    void (execute_ c "CREATE TEMPORARY TABLE zero_dates \+                     \(id INT, d DATE, dt DATETIME, ts TIMESTAMP NULL DEFAULT NULL)")+    void (execute_ c "INSERT INTO zero_dates VALUES \+                     \(1, '0000-00-00', '0000-00-00 00:00:00', '0000-00-00 00:00:00'), \+                     \(2, '2024-00-00', '2024-05-00 13:14:15', NULL), \+                     \(3, '2024-02-30', '2024-02-30 01:02:03', NULL)")++-- | The dates as the server prints them, which no decoding can clamp.+storedAsText :: MySQLConn -> L.ByteString -> IO [[MySQLValue]]+storedAsText c condition = do+    (_, rows) <- query_ c (Query ("SELECT CAST(d AS CHAR), CAST(dt AS CHAR), CAST(ts AS CHAR) \+                                  \FROM zero_dates WHERE " <> condition <> " ORDER BY id"))+    Stream.toList rows
+ test/ZeroDates.hs view
@@ -0,0 +1,65 @@+-- | Dates a 'Day' cannot hold, such as 0000-00-00, which used to come back+-- clamped to a real date (0000-01-01) and were written back that way.+module ZeroDates (tests) where++import           Data.Binary.Parser             (parseOnly)+import           Data.Binary.Put                (runPut)+import qualified Data.ByteString                as B+import qualified Data.ByteString.Lazy           as L+import           Data.Time.Calendar             (Day, fromGregorianValid)+import           Data.Word                      (Word16, Word8)+import           Data.Time.LocalTime            (TimeOfDay (..))+import           Database.MySQL.Field+import           Database.MySQL.Protocol.ColumnDef+import           Database.MySQL.Protocol.MySQLValue+import           Database.MySQL.Protocol.Packet  (putLenEncBytes)+import           Test.Tasty+import           Test.Tasty.HUnit++tests :: TestTree+tests = testGroup "zero dates"+    [ testCase "text DATE 0000-00-00" $+        assertEqual "value" (Right (MySQLDateRaw (RawDate 0 0 0)))+            (parseOnly (getTextField (column mySQLTypeDate)) (lenEnc "0000-00-00"))+    , testCase "text DATETIME with a zero day" $+        assertEqual "value" (Right (MySQLDateTimeRaw (RawDate 2024 5 0) (TimeOfDay 13 14 15)))+            (parseOnly (getTextField (column mySQLTypeDateTime)) (lenEnc "2024-05-00 13:14:15"))+    , testCase "binary TIMESTAMP of length 0" $+        assertEqual "value" (Right (MySQLTimeStampRaw (RawDate 0 0 0) (TimeOfDay 0 0 0)))+            (parseOnly (getBinaryField (column mySQLTypeTimestamp)) "\x00")+    , testCase "binary DATE past the end of its month" $+        assertEqual "value" (Right (MySQLDateRaw (RawDate 2024 2 30)))+            (parseOnly (getBinaryField (column mySQLTypeDate)) "\x04\xE8\x07\x02\x1E")+    , testCase "rendered back as the server stored it" $+        assertEqual "SQL" "'2024-05-00 13:14:15'"+            (runPut (putTextField (MySQLDateTimeRaw (RawDate 2024 5 0) (TimeOfDay 13 14 15))))+    , testCase "sent back in binary as the server stored it" $+        assertEqual "bytes" "\x04\x00\x00\x00\x00" (runPut (putBinaryField (MySQLDateRaw (RawDate 0 0 0))))+    , testCase "decoding one as a Day fails" $+        assertEqual "decoded"+            (Left (DecodeUnrepresentableDate (ExpectedTypeName "Day") (MySQLDateRaw (RawDate 0 0 0))))+            (fromField (MySQLDateRaw (RawDate 0 0 0)) :: Either DecodeError Day)+    , testCase "calendarDate agrees with fromGregorianValid on every month and day" $+        take 5 (filter disagrees calendarSample) @?= []+    ]++-- | Two full 400-year Gregorian cycles from year 0, the turns of centuries+-- around them, and the ends of 'Word16', each with every month 0 to 13 and+-- day 0 to 32, so that the fast path and its fallback both run.+calendarSample :: [RawDate]+calendarSample = do+    year <- [0 .. 800] ++ [1582, 1858, 1900, 1970, 2000, 2024, 2100, 9999, 65534, 65535 :: Word16]+    month <- [0 .. 13 :: Word8]+    day <- [0 .. 32 :: Word8]+    pure (RawDate year month day)++disagrees :: RawDate -> Bool+disagrees raw@(RawDate year month day) =+    calendarDate raw+        /= maybe (Left raw) Right (fromGregorianValid (toInteger year) (fromEnum month) (fromEnum day))++column :: FieldType -> ColumnDef+column fieldType = ColumnDef "" "" "" "" "" 63 0 fieldType 0 0++lenEnc :: B.ByteString -> B.ByteString+lenEnc value = L.toStrict (runPut (putLenEncBytes value))
+ test/cert/ca-key.pem view
@@ -0,0 +1,27 @@+-----BEGIN RSA PRIVATE KEY-----+MIIEpAIBAAKCAQEAro5f0Xvee3jDWdcbfAUriec9Hle2gwGH1gWhkVJvt8e6WfSi+LJEwEQJozgj3Mo8LNcd6J1/CwD7njNXE7IoGV9mqaC2b15EwzSkVFGz7M9EFH3rK+PsipFIlevgCNpIvjHPIaGXJ6g5iVm4MxnG1k5TjNrJFHV7MeYi/aJRaLQ1ZStMRS+U8e8b59XOzJ9jJBYE+p+KWWOzRMNkIUJEM1gOPSUrWChwD01LzMeyInpEkH305Or+DTF4KfUXqWD9xMk1/l3Qk0yMWi6nEjNb3oI6yk3VzXKv6UAuy5XlEYF7ldLKezCb+4sBXPzVXMu7TCHnQR7R4X/3pIQSIaezDvNil4QIDAQABAoIBAQCNIoasvQe1M35X+3InXa+K5HKOZLfhe0lT/IgxxkILNyIqPAzJA8J85kYYl2K/uTCQXNpM41L+FaxcT+EyCm1C6ux0p18zKHSFh5+VaW7krpqX4H5uMxglLbTPqyi4X63jQiikm5+4q9bhjA+5YGuZfDnOVdqyQgzC5tsbh5mUGUHaqREdecoWPkE5etId+zWOgq2yVTYgJaTYga1+sS9rtlHQTdgI5ZuEQe11X8JT40ghApo5glXXZnSPWeSA4hNJIKKTBllxrwBG0ZYI+BVf/pSiGciEz+fYrzv9JmPTjWEWutyr2AV5OMMnv1HTY5pkFFmylQTU8AHjFfUGD+Vlf7xmpFAoGBAOb0FZqBs6HWkU4FnWKe+48sO1bMEsAu3MO6Nk+o3eTd47J+bSXe+17UyGDSzGdnH1MxVeHmEqmfiXwkP9LEVr5ywbEqgzxFyRpXOXW+DxWs2boCJoGdW+FDdzjcJxigub/auSJasynowOjpYWLvWhlutVlhXC0XOhTejPUTh/xGKLAoGBAMF8+iuRLnIGIJ9anYo4RsKp26g/wW9JcwZS1HOeKH0sqmeFGwZDoDT2Dzq844tWSMfF0+eFoGWYZkvrtFogWGPjTLDWbagErDkSP6Y9qZkQFv+68nT8/G4k322ec6jx9H0/Z7+aWhlvGzPSpOnZc6XSu40MhMlCbCjPySr4To6hYLDAoGAZVReTiXt/Gm54vOPs/9w++8Y6WsC60Vs+PRnsTlMW2B2Kk3vow57cIYHTIPwbsXN9qBEwBJDyexXVAJLhlvie+zi6RtMSNVWhAE+YxFIZKDdoZ7Yd0uyIHSLUIU3GhUaqlR6udn1lcOrMAWFVvURIO+tz4b1XxejU8OccDUL+fO8DUCgYB+oM6ZiK6dy4kfH58NkaBIf9jBo7HEY/ZBJ6MK+GchPPktFNzR5bJU1vCypWpaB5dMPzgTyDhPLM+Xr1C+sY+YUUQutU7UAuSslG7r8+FuLiyPIz+ItwVR5InhSQEzAV7cFYH/6lV2fQ4n1RUw36/eMJMFnRU70awEwT6PVX+UXONVwKBgQCgVuQEukqt9NB8Hjte9w4MhAR22ML9XUvlDbQM2LL/eKj166SxXZqL+dPaV2pcBvET1NT4UMnSZTTS4ZqsTuuE6B9Dm9lWUqMPbBDtBKykOUWSTY88rRXSW+X0HdhQ/ycxHGN8Iv/wVZKo1ZSgEcEUM4jGvWclMgsnkWuaLBZPF/dA==+-----END RSA PRIVATE KEY-----
+ test/cert/ca.pem view
@@ -0,0 +1,21 @@+-----BEGIN CERTIFICATE-----+MIIDfzCCAmegAwIBAgIJAJkP4Ih73MLQMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV+BAYTAkNOMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX+aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMMBldpbnRlcjAeFw0xNzA3MjUxMjU3NDRa+Fw0yNzA2MDMxMjU3NDRaMFYxCzAJBgNVBAYTAkNOMRMwEQYDVQQIDApTb21lLVN0+YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMM+BldpbnRlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK6OX9F73nt4+w1nXG3wFK4nnPR5XtoMBh9YFoZFSb7fHuln0oiyRMBECaM4I9zKPCzXHeidfwsA++54zVxOyKBlfZqmgtm9eRMM0pFRRs+zPRBR96yj7IqRSJXr4AjaSL4xzyGhlyeoOY+lZuDMZxtZOU4zayRR1ezHmIv2iUWi0NWUrTEUlPHvG+fVzsyfYyQWBPqfilljs0T+DZCFCRDNYDj0lK1gocA9NS8zHsiJ6RJB99OTqw0xeCn1F6lg/cTJNf5d0JNMjFou+pxIzW96COspN1c1yr+lALsuV5RGBe5XSynswm+LAVz81VzLu0wh50Ee0eF/96SEE+iGnsw7zYpeECAwEAAaNQME4wHQYDVR0OBBYEFCj3eNkh8tyrzQMsEKirVA1ilVZA+MB8GA1UdIwQYMBaAFCj3eNkh8tyrzQMsEKirVA1ilVZAMAwGA1UdEwQFMAMBAf8w+DQYJKoZIhvcNAQELBQADggEBAFUf6Q6Og8PKYlGFLq177k+DIBJKMXqDUIWuZq/Z+o11vks/4uE9LRXkicPJUbjK7Iv6mw5gHwYSxAkrJaeY6wQpiV8PoSOD1AIm448da+4SLLJMnB4vPPqrnDhoHNNOWonsZYyV6avD97+kTxM6WgJYhdwM4L1mxGGGT37nuf+JtcZHLnI3PrHj3M89xwk4BFRXnb6+X/uWZxeF0AFCWZgMtzB+iMMz0VnBmBst8RI+Xs+gYzBXN1Mq1KMcSP5r+BGE1GAM46Q5EEI29M/5Fw/38MoZlMvg4K+rdqg7GAKF+fM3q9cMZ7Uv924KcQmF5ZKoZjFjp/PfaC8WZplF6yH+y3gA=+-----END CERTIFICATE-----
+ test/cert/client-cert.pem view
@@ -0,0 +1,21 @@+-----BEGIN CERTIFICATE-----+MIIDczCCAlugAwIBAgIBAjANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjET+MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ+dHkgTHRkMQ8wDQYDVQQDDAZXaW50ZXIwIBcNMjYxMTExMTg0OTQ0WhgPMjEyNjEw+MTgxODQ5NDRaMDExFjAUBgNVBAoMDW15c3FsLWhhc2tlbGwxFzAVBgNVBAMMDnRl+c3RDbGllbnRDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtQgI+BhdPpmgBCHYsE+S8ooknz3zZKzqjoU6MVG9uTlJJs7/gKMR1yK7FMQbA042Vzzuz+Z90rUiXe/w37CVPZJttTsQmxkqC6TQ1JXWOH9fbU8SpTibaYz1SntX7p4sThavX0+CEWvsP2qkUy1x+b+pJueFQDF/sKNNmO20nasjin9XBbmm1W0E/iF3/ONVjVLwuE4+Rf9vCLO1sMW7an45FRSpq3UjXGxT/uWYrNbkuH00Xn2AyGRi3Y4sjtq4eLfcCOoG+c7lCU6YG3JNpW2wg3E5aU1l5+F6/GNFFxAL6v0HvBVLUrBzl4FAwEA4xeFGhHJQA+t6KzrhHNRXAhvVmBgwIDAQABo28wbTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIHgDAT+BgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQUtTY22oCrl28tpaNsocAm9sre+kcowHwYDVR0jBBgwFoAUKPd42SHy3KvNAywQqKtUDWKVVkAwDQYJKoZIhvcNAQEL+BQADggEBAFFu6BGMSo+nW8F10teuJJ80DH3QlslGrgQ99FOlIclk/gCM/Qf+Y0tG+wOrbYpd2Y5kc/FFFPp2NS9LpbgrvaKKmHnfdw/RcvtNqnnAcNk/tfcNxyBG87abE+C1d3p4N78uafV3MZMjHMGYKX4/yQSfYd5wgI3FAoP0FVTNLR1lduimN/HKY0Jdqh+jkg8xr/DOae0Z9JbdtsVpr6qHDwOnveQjEfddu2q2T+pzlDNkAVUTTilYKr0PSaJ++6KiybDpn2H3a4ZZf14pVGTCAji6DyBjTtbGGDZywmHYbiG/PYV+huYbyOurJkTh+TL5XD03iaChi7lTi+ZU2j07NNgHbVmk=+-----END CERTIFICATE-----
+ test/cert/client-key.pem view
@@ -0,0 +1,27 @@+-----BEGIN RSA PRIVATE KEY-----+MIIEpAIBAAKCAQEAtQgIBhdPpmgBCHYsE+S8ooknz3zZKzqjoU6MVG9uTlJJs7/g+KMR1yK7FMQbA042VzzuzZ90rUiXe/w37CVPZJttTsQmxkqC6TQ1JXWOH9fbU8SpT+ibaYz1SntX7p4sThavX0CEWvsP2qkUy1x+b+pJueFQDF/sKNNmO20nasjin9XBbm+m1W0E/iF3/ONVjVLwuE4Rf9vCLO1sMW7an45FRSpq3UjXGxT/uWYrNbkuH00Xn2A+yGRi3Y4sjtq4eLfcCOoGc7lCU6YG3JNpW2wg3E5aU1l5+F6/GNFFxAL6v0HvBVLU+rBzl4FAwEA4xeFGhHJQAt6KzrhHNRXAhvVmBgwIDAQABAoIBAAIbjNRdUx0R2uk5+TvwQTq3pwN/miTS6owsOug2Fmpgo1xjTpA11dBWZC0yt0nLkFBlsjYMpDQcQz87S+X9jU9BNCvG+0ODAH1QNuoysGLKANdp80KfLRgGfxiV5QkvKD7kfrgyLRhOEfouu8+I+Wm59IMycNe1e9IXpKCiK9cmFezWm6Jmd8nyXBk9JnrhkQvRWP6dHY01c4bWLUW+fZqF47naM2gwe5f3PxhiGF3MWyb2R8z82dgRpQH+tL7GY6Q4OW1XdzVgj994+zXT+MH2dhV0IuEKYxbLYG8L9Pr5rDj0VJ+ljcclkJEm/w56nyfdoyCUoAowzZASNPNxV+tJkgaykCgYEA3+XZGNYnAHN8CfnooPfWMA7uudcTtqwcn9p7xiV6PayRMQbYrlCD+/uhBh3TynO27bKB9EdIlOKM4XeFALcKmkY+/6gG+3quZ5G00bO519Mm0Q+TSOU/K++v1qa6yf22MJe/oP880ovky2jXMpXsg/SqQ1zY2nAqstJov75wjg//UCgYEAzvzF+4v1UFIfccYoRkjZDvQDVJ/u9FFAgCfK90LGOzYdn3y413JIrFHUxZ3tshGn7kaJF+RWJ0fIz38xaTWzLs1oegsyU4ti6jjzUxP+4hy+2qw8nHPMLP9Lltpt7AgvZtIKjV+xsFzwklu5pUKMTy87iUbh/qyI8cV1YE7jKCraJcCgYEA0XsWnOM4yHZEVn0ohvp6+vqop91xgyGfNvE5JwgEslumPSqSSNL2GUgfxz4++ss7GrgKWIgdQ5eXohgWK64WU+HNq2/3U0pNc++/akt0qAuE/DxkYf3plmW2Cgoi93vWay5/j11fBcr3fM0RkvWsvW+U7rB0Bk5agjIDKmqPOYUxcUCgYAuJ+OgqEjVgy4/C0C7z+nu/X6KufIB05QxY7W8+Ebv8TNN2ywErvFRq7W8k2jrqlA+K/BpiTIAZPf4UcNZPId36QKgvCn/I8EF8r6nZ+zaovpUHNAvOwsuhYc/jlmwpmqbjP7ZYuCflcO6/FLzrkm9ytZgzrl5WdsMlHoNe8+lh9i5QKBgQCMrstELUEbNeKsIpd1XpUMFSK6A1xmgQr73F5HWossN27tYcn0lxpj+zjSk5eVlWfSzoHArPSvcCwbbbykVmsd0OrvC2TEL9TArKJyxZo069TFSsgYWHYlc+zHFW8l4dpy0Li43tg/CxxqyKBPlm2wIVmTj2nU/hLVXnlhgqaTqlMw==+-----END RSA PRIVATE KEY-----
+ test/cert/server-cert.pem view
@@ -0,0 +1,20 @@+-----BEGIN CERTIFICATE-----+MIIDQTCCAimgAwIBAgIBATANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjET+MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ+dHkgTHRkMQ8wDQYDVQQDDAZXaW50ZXIwHhcNMTcwNzI1MTI1ODExWhcNMjcwNjAz+MTI1ODExWjBWMQswCQYDVQQGEwJDTjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8G+A1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDDAZXaW50ZXIw+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCD3Wlrhbz++Z360JuvsXD+M/CMquiGJU9XSA35g5lw7nIEsNYKzOW3dGuTzTLo/ZCw006H7eLVIZn0AZVSBSnk+teEvOHUdn56E3XVvCGBxBdbTbVrT+2hDClVg230102SYLqNwwH1fOCGVnq74sHSA+86FRLlt+uFuZCpflaXsZ6lz/dNWAP4Lwu2bA05YJAn3mxEkrlxxbsLL4X+MKpUMg+WRed+eykaFTY8G+pXmJZPLYblv5Vy+Jm5sbBuIqUElFkfHtHTpdCvahe2hcOmDcG+eKmoSav2kolFb1xov7tOXCC9DdhfuNPLE0KGAWBLu8nMDmk7apOI33U6+4OCS8Xn+AgMBAAGjGjAYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMA0GCSqGSIb3DQEBCwUA+A4IBAQBOKHB1StdKjQzk9cDFroR8jvbTb0PTANQYFsyAtN/VXcRLK9GxkW8Hxn/o+SO+gTStOUu4ZaS6EymJ/z0z1oHDTCifoPo3R9o3SCUH7XDM3PV3ulgG59c2tfzRw+dpK+ttfCe1e228YKfP3WI1hNnMjVnijKCJMROumQiY4CQhtUDdaAdK5jF2yvtThb+kyHuimeIoyBESBmPuGnkFtcwzxXW43Qt9Y2uJg7WxyEgoNfXL73Enx++Hv6UJSPM+sUPItS8M5wFoNJw4hpjPvXxQLrVnWd2Qs8p3LmohBpogg9C7GTzTrsM3wySxRTEA+rrbjCtYnbrs4+HD9mlbzKi9mrcXi+-----END CERTIFICATE-----
+ test/cert/server-key.pem view
@@ -0,0 +1,27 @@+-----BEGIN RSA PRIVATE KEY-----+MIIEogIBAAKCAQEAwg91pa4W8/vmd+tCbr7FwzPwjKrohiVPV0gN+YOZcO5yBLDW+Cszlt3Rrk80y6P2QsNNOh+3i1SGZ9AGVUgUp5LXhLzh1HZ+ehN11bwhgcQXW021a+0/toQwpVYNt9NdNkmC6jcMB9XzghlZ6u+LB0gPOhUS5bfrhbmQqX5Wl7Gepc/3TV+gD+C8LtmwNOWCQJ95sRJK5ccW7Cy+F/jCqVDIFkXnfnspGhU2PBvqV5iWTy2G5b++VcviZubGwbiKlBJRZHx7R06XQr2oXtoXDpg3BnipqEmr9pKJRW9caL+7TlwgvQ3Y+X7jTyxNChgFgS7vJzA5pO2qTiN91OvuDgkvF5wIDAQABAoIBAFG0gpscPivBrlpl+IhZQmiFC2A/R0Y7RXKNqAgBA6TTEIBtbfPMVK87e5IBZUAsK34WjohfXD78eo5w5+om3jIK1b6pCgG6c6Ulqsh0oVY82SYM2p6CkCW/aF/kaGgyRLebodQNEzKlYR4woE+wrSQTq0QZqYwd3NWGbLI5AVCMQNKjk/ogHOGAxwNGAzB3KHG55zQL68iu77vSJFa+gf9MtSgaqxosWedtdRWVo9N2jm3BhrebK85J5bZQUp6xHvHSgbW/PGWQMiJUzuzo+wzmBhkE4RM5hgBtBBUs6lP/trxzl1eKEkDSQdsxpZ5bIz0dhe3ZUqHM/apTVoD1n+OBYTfWkCgYEA8Ue08d2C44wJTqyY1a+Tf0kSrNFEYjqWTKe19nzlQ7mFoUP6ORIw+z6pLvliFSFFxQrB2uAzgJGIYdUY62XCV7Cm7gIfQxJEv2jUJ8ZNFTyZGKvBVxQu6+98CDkmsK5Zgjd/fhZb/7c6a9tdNm6C0Z/tcvi/CqhCiK5UmBYTXOeuUCgYEAzeZH+buv65edEsxesHxZjSFIekEnvUUQw8JpRn/Cr9nzOem9quBwp85PNAoEPkBiDeq6C+2xE6wFO0C17cw5oPahkTg0C+PbKmNA3HWZLYXjAEke+bXponEGiBKtrcuoXPiRVi+dLXVQyZ09sSd9QFVz0zb+yhnxmB8ckz6m3Qn1NsCgYBiMBo0qz5Ot3g6KgbwPsk5+bVVKOscnakwr2bw1GNJ74w28eNtlSj/O4rB8P63Npkb/KqzFbYfxhnIJl5DvV9/L+AZiqT5rnw2XD2P2474OvGBZJ9xAQZhuqIw/oD+OZV3Znpdp1+9CqWdquGd+w8Fm5+2Xwh6Mibi5wqfuVF2A20+QKBgCXlQjR6Hj1+KTjFHJrEJhXDd3ibAv70DAXcgTVs+uFmvctk59GxaUqZQB6V0V4bv3BMSV2c2bpfDUBnfpcYibr/aSasYmWsFnG1sM8qa+qlP7hnvNm8z3lkUA1vUqPwHdausSEoLkjJHFXDWmzmOvkTmMLdi2AROT4pOu81j1+MfeNAoGAIbS1Gngub9JDeoyWPRPNkZaNXIaPdkXOXmwJvigbnWstgbHsgu24HfJc+LCkS8MIYWVSbceDZflkSaJ9ZF0dGEJPPNdsfPi3x2iWTYHp8as9+8JnSsG4C8fvA+gQZLrF0JxP7B4ZmHYNawtQBVnrcKldwCuotP16V7lH+we3xuezQ=+-----END RSA PRIVATE KEY-----
+ test/cert/server-req.pem view
@@ -0,0 +1,16 @@+-----BEGIN CERTIFICATE REQUEST-----+MIICmzCCAYMCAQAwVjELMAkGA1UEBhMCQ04xEzARBgNVBAgMClNvbWUtU3RhdGUx+ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAwwGV2lu+dGVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwg91pa4W8/vmd+tC+br7FwzPwjKrohiVPV0gN+YOZcO5yBLDWCszlt3Rrk80y6P2QsNNOh+3i1SGZ9AGV+UgUp5LXhLzh1HZ+ehN11bwhgcQXW021a0/toQwpVYNt9NdNkmC6jcMB9XzghlZ6u++LB0gPOhUS5bfrhbmQqX5Wl7Gepc/3TVgD+C8LtmwNOWCQJ95sRJK5ccW7Cy+F/j+CqVDIFkXnfnspGhU2PBvqV5iWTy2G5b+VcviZubGwbiKlBJRZHx7R06XQr2oXtoX+Dpg3BnipqEmr9pKJRW9caL+7TlwgvQ3YX7jTyxNChgFgS7vJzA5pO2qTiN91OvuD+gkvF5wIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAIjdOfU/dtQcP/Da1IIXlLji+wFCHk5/jlC+oxSIXKtF+3ZBrCx3R/pm7V9ERbeJWXswlhd+8Rkeyde+L3PnDly77+F2oKa1jKcp3lituP1V7tVM8WvScZbLC3RAKFioapN9+Jbwhs8DVtKryNiAtndjFl+jRG3+7xS5rOijgVTStzqvYfRqcUJvvgdT62EV2oSq59ZjNmYkvj8ZAUuI8feKDNm+TdTdGnaC0Br83XNiTKtsSLKk9p0WudzZkiCaeR07hOA1AoJ30DSnsQE+BG/5f/nn+1RbEybGDaFPTULYGXsoZ4TTcr5sxgfXmhomBn/iF/Ns5LEvNosKqwOOPPM4P0eI=+-----END CERTIFICATE REQUEST-----
+ test/json-data/buffer-builder.json view
@@ -0,0 +1,4322 @@+[+  {+    "_id": "54727a90e43524efe8abfdac",+    "index": 0,+    "guid": "aaa926e8-8359-4afe-adb0-dca0401de765",+    "isActive": true,+    "balance": "$1,114.73",+    "picture": "http://placehold.it/32x32",+    "age": 20,+    "eyeColor": "green",+    "name": "Dillon Valenzuela",+    "gender": "male",+    "company": "GEOFORM",+    "email": "dillonvalenzuela@geoform.com",+    "phone": "+1 (817) 536-3796",+    "address": "309 Desmond Court, Zarephath, Montana, 6156",+    "about": "Dolore laboris excepteur sunt velit velit nostrud non esse qui laboris nostrud Lorem. Amet anim occaecat et ipsum. Ad proident nostrud elit adipisicing anim labore consectetur ut amet elit. Tempor aliqua in ea eiusmod elit Lorem cillum est laboris. Nisi Lorem amet amet Lorem. Nostrud sunt incididunt magna nostrud amet enim. Laboris ad et fugiat dolor ad proident aliquip nisi do ea eiusmod enim proident.\r\n",+    "registered": "2014-08-17T07:29:03 +07:00",+    "latitude": -17.423462,+    "longitude": 121.069662,+    "tags": [+      "sunt",+      "ex",+      "irure",+      "cillum",+      "velit",+      "duis",+      "sit"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Joyce Jordan"+      },+      {+        "id": 1,+        "name": "Ophelia Rosales"+      },+      {+        "id": 2,+        "name": "Florine Stark"+      }+    ],+    "greeting": "Hello, Dillon Valenzuela! You have 9 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90f520a07ff2498a69",+    "index": 1,+    "guid": "8f78cc18-a7ef-498b-9adf-60f008a48c02",+    "isActive": false,+    "balance": "$1,324.52",+    "picture": "http://placehold.it/32x32",+    "age": 33,+    "eyeColor": "blue",+    "name": "Donovan Weiss",+    "gender": "male",+    "company": "TELPOD",+    "email": "donovanweiss@telpod.com",+    "phone": "+1 (962) 417-2408",+    "address": "511 Grant Avenue, Westmoreland, Hawaii, 5092",+    "about": "Laborum excepteur exercitation sint veniam dolor. Dolor aliqua esse do aliqua adipisicing. Ea magna minim veniam tempor ipsum. Incididunt anim reprehenderit occaecat proident eiusmod anim Lorem aliquip dolore incididunt. Consequat occaecat qui minim commodo est velit ex.\r\n",+    "registered": "2014-09-30T23:49:41 +07:00",+    "latitude": -18.034963,+    "longitude": 70.821705,+    "tags": [+      "cupidatat",+      "excepteur",+      "id",+      "consectetur",+      "ea",+      "enim",+      "et"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Cummings Irwin"+      },+      {+        "id": 1,+        "name": "Conley Lester"+      },+      {+        "id": 2,+        "name": "Katy Holt"+      }+    ],+    "greeting": "Hello, Donovan Weiss! You have 2 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90fbb3f49ea6d40dde",+    "index": 2,+    "guid": "69a4c6d7-6189-4a90-ae00-7b64ab1d5a2d",+    "isActive": false,+    "balance": "$2,739.87",+    "picture": "http://placehold.it/32x32",+    "age": 33,+    "eyeColor": "brown",+    "name": "Renee Calderon",+    "gender": "female",+    "company": "TALKOLA",+    "email": "reneecalderon@talkola.com",+    "phone": "+1 (910) 523-3382",+    "address": "323 Montauk Avenue, Greenwich, South Dakota, 8034",+    "about": "Reprehenderit deserunt et do proident Lorem Lorem dolor labore ullamco eu nulla laborum voluptate. Consectetur sint magna enim commodo dolore ullamco in. Nulla id proident esse veniam proident esse velit exercitation do. Qui nulla magna anim Lorem dolor culpa id. Nostrud cupidatat officia culpa sit irure voluptate exercitation voluptate nostrud elit. Duis occaecat velit fugiat exercitation anim aute laborum irure.\r\n",+    "registered": "2014-09-11T20:48:20 +07:00",+    "latitude": 15.654081,+    "longitude": 84.156609,+    "tags": [+      "dolor",+      "et",+      "in",+      "veniam",+      "non",+      "est",+      "enim"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Gilda Murray"+      },+      {+        "id": 1,+        "name": "Marguerite West"+      },+      {+        "id": 2,+        "name": "Chrystal Rojas"+      }+    ],+    "greeting": "Hello, Renee Calderon! You have 1 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a90c5ea2a48502a0640",+    "index": 3,+    "guid": "689a64d7-16b0-41dc-947d-035b40a29ed4",+    "isActive": false,+    "balance": "$3,805.39",+    "picture": "http://placehold.it/32x32",+    "age": 32,+    "eyeColor": "blue",+    "name": "Aguirre Garrett",+    "gender": "male",+    "company": "ZOSIS",+    "email": "aguirregarrett@zosis.com",+    "phone": "+1 (811) 471-3145",+    "address": "198 Baycliff Terrace, Clarence, District Of Columbia, 1528",+    "about": "Deserunt minim aliquip magna ut fugiat quis. Quis consequat culpa reprehenderit minim do eiusmod adipisicing nostrud aute. Lorem nostrud laborum quis dolore tempor tempor magna aute eu pariatur dolor est. Reprehenderit ex consectetur adipisicing Lorem nostrud fugiat aute non quis anim laborum aute proident amet. Sunt consectetur eu ut eu mollit ipsum.\r\n",+    "registered": "2014-02-14T04:27:27 +08:00",+    "latitude": 25.922024,+    "longitude": 107.428199,+    "tags": [+      "esse",+      "officia",+      "commodo",+      "esse",+      "cupidatat",+      "amet",+      "ad"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Penelope Humphrey"+      },+      {+        "id": 1,+        "name": "Church Byers"+      },+      {+        "id": 2,+        "name": "Hazel Stafford"+      }+    ],+    "greeting": "Hello, Aguirre Garrett! You have 4 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a9096bcf2d2f4b4aeff",+    "index": 4,+    "guid": "77392422-81ad-48c8-9cfc-37d142a814d5",+    "isActive": false,+    "balance": "$1,956.06",+    "picture": "http://placehold.it/32x32",+    "age": 26,+    "eyeColor": "brown",+    "name": "Griffin Mclaughlin",+    "gender": "male",+    "company": "COMTOUR",+    "email": "griffinmclaughlin@comtour.com",+    "phone": "+1 (950) 494-2956",+    "address": "549 Robert Street, Libertytown, North Carolina, 4377",+    "about": "Exercitation culpa ad id deserunt sit ullamco anim anim ad aute officia ut tempor. Ullamco dolor pariatur enim exercitation commodo consectetur fugiat velit nulla mollit pariatur occaecat. Amet voluptate qui labore mollit ipsum consequat. Esse pariatur proident ad pariatur aliquip labore quis voluptate quis ut culpa tempor. Reprehenderit sint id officia deserunt quis laboris. Velit ipsum dolore anim consectetur.\r\n",+    "registered": "2014-06-23T04:48:12 +07:00",+    "latitude": 8.808284,+    "longitude": 20.539814,+    "tags": [+      "consequat",+      "proident",+      "eiusmod",+      "dolore",+      "in",+      "laborum",+      "non"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Hinton Santiago"+      },+      {+        "id": 1,+        "name": "Talley Dyer"+      },+      {+        "id": 2,+        "name": "Karen Logan"+      }+    ],+    "greeting": "Hello, Griffin Mclaughlin! You have 3 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a9010e5c5ac55919561",+    "index": 5,+    "guid": "7e6fca4d-0329-47ab-b3e7-f4d6a6ee4dd2",+    "isActive": true,+    "balance": "$3,767.46",+    "picture": "http://placehold.it/32x32",+    "age": 36,+    "eyeColor": "brown",+    "name": "Rachel Mcclure",+    "gender": "female",+    "company": "ESCENTA",+    "email": "rachelmcclure@escenta.com",+    "phone": "+1 (857) 518-3454",+    "address": "901 Harwood Place, Rossmore, Tennessee, 3213",+    "about": "Nisi enim deserunt ut ex mollit fugiat. Magna nostrud culpa irure nulla mollit nisi. Sit ea incididunt amet culpa veniam voluptate Lorem sunt dolore amet. Exercitation tempor eiusmod exercitation officia dolor. Ut ea sint veniam cupidatat et. Sunt nostrud ad ipsum non elit elit esse ut dolor. Et officia et minim ipsum amet sint eu ut nostrud adipisicing cillum ullamco.\r\n",+    "registered": "2014-05-29T20:42:11 +07:00",+    "latitude": 8.132878,+    "longitude": -99.198755,+    "tags": [+      "est",+      "Lorem",+      "ullamco",+      "nisi",+      "reprehenderit",+      "nisi",+      "cupidatat"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Michael Martin"+      },+      {+        "id": 1,+        "name": "Gayle Heath"+      },+      {+        "id": 2,+        "name": "Slater Bender"+      }+    ],+    "greeting": "Hello, Rachel Mcclure! You have 2 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90a76a5ba627b180c9",+    "index": 6,+    "guid": "fbf59485-142c-4eb6-a98e-8952cbb19942",+    "isActive": true,+    "balance": "$2,139.12",+    "picture": "http://placehold.it/32x32",+    "age": 28,+    "eyeColor": "blue",+    "name": "Megan Morrow",+    "gender": "female",+    "company": "PETICULAR",+    "email": "meganmorrow@peticular.com",+    "phone": "+1 (825) 572-2854",+    "address": "151 Jardine Place, Boonville, Idaho, 160",+    "about": "Magna reprehenderit aliqua quis eiusmod eu minim ut. Consectetur non veniam est et aliqua mollit. Aliqua eiusmod sit fugiat qui quis nisi mollit officia do. Enim quis quis occaecat ullamco anim reprehenderit exercitation. Ad id id laborum ipsum ipsum cillum ipsum occaecat irure. Commodo voluptate ullamco excepteur ea laboris adipisicing veniam cillum commodo velit anim. Occaecat magna laborum non ex anim consequat dolor cillum excepteur velit duis commodo officia veniam.\r\n",+    "registered": "2014-04-16T08:56:52 +07:00",+    "latitude": 57.771446,+    "longitude": -146.885322,+    "tags": [+      "veniam",+      "officia",+      "magna",+      "ipsum",+      "nulla",+      "ea",+      "mollit"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Marina Wise"+      },+      {+        "id": 1,+        "name": "Ann Mercado"+      },+      {+        "id": 2,+        "name": "Glenn Duran"+      }+    ],+    "greeting": "Hello, Megan Morrow! You have 7 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a90776f7328f4df750f",+    "index": 7,+    "guid": "ef99cc91-061c-49c5-9901-3ac22a75bc92",+    "isActive": true,+    "balance": "$3,161.69",+    "picture": "http://placehold.it/32x32",+    "age": 37,+    "eyeColor": "blue",+    "name": "Lawson Kirby",+    "gender": "male",+    "company": "PLASMOSIS",+    "email": "lawsonkirby@plasmosis.com",+    "phone": "+1 (853) 583-2577",+    "address": "122 Linden Boulevard, Edneyville, Palau, 6050",+    "about": "Elit ullamco reprehenderit eiusmod in duis minim exercitation incididunt reprehenderit eiusmod nulla. Cupidatat ea anim incididunt esse ea eu esse laboris velit. Culpa sunt exercitation officia amet voluptate enim duis ipsum ex anim quis nisi. Dolor officia irure do veniam do amet ullamco eu.\r\n",+    "registered": "2014-08-13T02:14:13 +07:00",+    "latitude": -16.166232,+    "longitude": 12.254111,+    "tags": [+      "et",+      "aliqua",+      "aute",+      "voluptate",+      "officia",+      "est",+      "quis"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Marquita Hutchinson"+      },+      {+        "id": 1,+        "name": "Karla Conner"+      },+      {+        "id": 2,+        "name": "Mattie Page"+      }+    ],+    "greeting": "Hello, Lawson Kirby! You have 3 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a90b8e9fff0ed297a59",+    "index": 8,+    "guid": "9bb8d813-704c-4714-929f-1321576a2701",+    "isActive": false,+    "balance": "$1,275.37",+    "picture": "http://placehold.it/32x32",+    "age": 23,+    "eyeColor": "brown",+    "name": "Pat Medina",+    "gender": "female",+    "company": "PLEXIA",+    "email": "patmedina@plexia.com",+    "phone": "+1 (933) 557-3028",+    "address": "623 Sunnyside Court, Edinburg, Minnesota, 2620",+    "about": "Est aliqua sint adipisicing labore magna sunt laboris non eu. Aute deserunt commodo Lorem amet sunt amet qui qui incididunt anim Lorem culpa. Ea exercitation officia dolor aute incididunt ea enim. Nulla aliqua ut ullamco sunt velit cillum esse minim incididunt ut mollit aute. Fugiat eiusmod anim laborum duis aliquip aute reprehenderit qui dolor officia amet labore id. Enim officia reprehenderit incididunt sit. Nisi ullamco nisi adipisicing esse eiusmod dolore veniam cupidatat consequat sunt.\r\n",+    "registered": "2014-10-22T20:29:14 +07:00",+    "latitude": -9.032987,+    "longitude": 71.98971,+    "tags": [+      "laborum",+      "dolore",+      "aliqua",+      "tempor",+      "eu",+      "elit",+      "voluptate"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Elnora Moses"+      },+      {+        "id": 1,+        "name": "Alexandra Carter"+      },+      {+        "id": 2,+        "name": "Beth Pacheco"+      }+    ],+    "greeting": "Hello, Pat Medina! You have 5 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a908c17a619571cfd39",+    "index": 9,+    "guid": "0a5de094-eadc-4120-8941-964f27fbc898",+    "isActive": true,+    "balance": "$1,939.34",+    "picture": "http://placehold.it/32x32",+    "age": 35,+    "eyeColor": "brown",+    "name": "Gibbs Butler",+    "gender": "male",+    "company": "OPTIQUE",+    "email": "gibbsbutler@optique.com",+    "phone": "+1 (956) 574-3434",+    "address": "552 Columbia Street, Richmond, Illinois, 903",+    "about": "Laboris eu ut magna exercitation non laboris sint. Mollit velit est exercitation enim exercitation reprehenderit culpa eiusmod laboris fugiat veniam adipisicing. Voluptate enim id do eu officia duis in incididunt. Esse do laboris pariatur cupidatat elit.\r\n",+    "registered": "2014-02-24T15:46:23 +08:00",+    "latitude": -68.874573,+    "longitude": -6.876165,+    "tags": [+      "excepteur",+      "deserunt",+      "ea",+      "labore",+      "aliqua",+      "deserunt",+      "labore"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Chen Langley"+      },+      {+        "id": 1,+        "name": "Latonya Vaughan"+      },+      {+        "id": 2,+        "name": "Simone Sutton"+      }+    ],+    "greeting": "Hello, Gibbs Butler! You have 2 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a9049447027a378812c",+    "index": 10,+    "guid": "406ae437-820d-4e0f-b8dc-1da0ddcc561c",+    "isActive": false,+    "balance": "$1,348.37",+    "picture": "http://placehold.it/32x32",+    "age": 39,+    "eyeColor": "brown",+    "name": "Bond Peck",+    "gender": "male",+    "company": "DANCERITY",+    "email": "bondpeck@dancerity.com",+    "phone": "+1 (918) 554-2856",+    "address": "361 Cortelyou Road, Hollins, Missouri, 131",+    "about": "Nisi ut ea ut deserunt. Consequat pariatur dolor consequat sunt. Ullamco aute proident excepteur fugiat id consequat eu nisi occaecat.\r\n",+    "registered": "2014-05-15T02:12:14 +07:00",+    "latitude": -79.266187,+    "longitude": 92.89454,+    "tags": [+      "ex",+      "est",+      "sit",+      "irure",+      "qui",+      "veniam",+      "exercitation"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Nettie Pope"+      },+      {+        "id": 1,+        "name": "Alyssa Hensley"+      },+      {+        "id": 2,+        "name": "Puckett Kinney"+      }+    ],+    "greeting": "Hello, Bond Peck! You have 7 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a9062a82eff9506c4d7",+    "index": 11,+    "guid": "b7400b79-2b71-46cd-9260-13e0767b437e",+    "isActive": true,+    "balance": "$1,757.66",+    "picture": "http://placehold.it/32x32",+    "age": 20,+    "eyeColor": "blue",+    "name": "Garrison Warner",+    "gender": "male",+    "company": "NAMEBOX",+    "email": "garrisonwarner@namebox.com",+    "phone": "+1 (874) 417-2183",+    "address": "927 Knapp Street, Shaft, Connecticut, 2188",+    "about": "Sint commodo ipsum commodo id magna labore in. Pariatur nostrud reprehenderit adipisicing amet enim ut quis laboris culpa exercitation deserunt cillum. Adipisicing excepteur sunt do labore anim exercitation non excepteur. Minim consectetur sit dolore occaecat et aliquip aliquip excepteur aliquip ut do eiusmod ea nulla. Nostrud sunt dolore ea sunt est enim occaecat culpa minim. Sit elit mollit mollit aliquip eu ex. Tempor nulla voluptate dolore ex.\r\n",+    "registered": "2014-06-18T03:58:19 +07:00",+    "latitude": -87.629414,+    "longitude": 96.653639,+    "tags": [+      "eu",+      "deserunt",+      "dolore",+      "nisi",+      "culpa",+      "ullamco",+      "ipsum"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Gutierrez Sears"+      },+      {+        "id": 1,+        "name": "Holly Jacobson"+      },+      {+        "id": 2,+        "name": "Henry Delgado"+      }+    ],+    "greeting": "Hello, Garrison Warner! You have 4 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a904638f48ca7f9e85e",+    "index": 12,+    "guid": "4cd59615-46b7-4733-a439-dc59c0ca0118",+    "isActive": true,+    "balance": "$3,897.06",+    "picture": "http://placehold.it/32x32",+    "age": 24,+    "eyeColor": "blue",+    "name": "Consuelo Bray",+    "gender": "female",+    "company": "COMTEST",+    "email": "consuelobray@comtest.com",+    "phone": "+1 (945) 432-2620",+    "address": "433 Clinton Street, Clarktown, Maine, 6916",+    "about": "Do occaecat consectetur tempor Lorem ullamco commodo exercitation veniam reprehenderit nulla officia. Nisi irure sunt cupidatat amet et excepteur quis et do. Et commodo nulla occaecat do sint quis mollit reprehenderit do laboris magna. Dolore ea sit aute esse consequat fugiat Lorem occaecat adipisicing adipisicing qui aute elit. Amet veniam anim ipsum aute officia. Fugiat nisi magna anim pariatur minim magna exercitation duis officia.\r\n",+    "registered": "2014-03-19T07:39:44 +07:00",+    "latitude": 7.760448,+    "longitude": -92.399314,+    "tags": [+      "irure",+      "veniam",+      "ex",+      "ullamco",+      "anim",+      "do",+      "enim"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Ellen Bullock"+      },+      {+        "id": 1,+        "name": "Davis Vaughn"+      },+      {+        "id": 2,+        "name": "Robert Mendoza"+      }+    ],+    "greeting": "Hello, Consuelo Bray! You have 8 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90fd85a45d33d54cf7",+    "index": 13,+    "guid": "53868eb7-df5c-445d-ad62-6124db081b6c",+    "isActive": false,+    "balance": "$2,988.04",+    "picture": "http://placehold.it/32x32",+    "age": 21,+    "eyeColor": "green",+    "name": "Clark Skinner",+    "gender": "male",+    "company": "OVOLO",+    "email": "clarkskinner@ovolo.com",+    "phone": "+1 (834) 587-2391",+    "address": "967 Dewey Place, Gibsonia, Puerto Rico, 6398",+    "about": "Duis irure exercitation ullamco amet aliquip minim sit consequat officia laborum id ad. Lorem duis velit consequat non irure nulla elit elit velit dolor ullamco qui fugiat. Quis ad velit ea consectetur veniam Lorem incididunt quis magna eiusmod. Et nostrud irure tempor nostrud esse.\r\n",+    "registered": "2014-10-11T06:48:59 +07:00",+    "latitude": -62.633073,+    "longitude": -121.03338,+    "tags": [+      "ut",+      "deserunt",+      "adipisicing",+      "enim",+      "amet",+      "proident",+      "officia"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Francine Petersen"+      },+      {+        "id": 1,+        "name": "Lena Robles"+      },+      {+        "id": 2,+        "name": "Mcintyre Gardner"+      }+    ],+    "greeting": "Hello, Clark Skinner! You have 6 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a908789850c55818269",+    "index": 14,+    "guid": "84cc7d0b-0544-40f8-900a-825429b0ee87",+    "isActive": true,+    "balance": "$3,012.07",+    "picture": "http://placehold.it/32x32",+    "age": 32,+    "eyeColor": "blue",+    "name": "Katie Allen",+    "gender": "female",+    "company": "SYBIXTEX",+    "email": "katieallen@sybixtex.com",+    "phone": "+1 (959) 504-2563",+    "address": "543 Heath Place, Bartonsville, New Mexico, 4882",+    "about": "Nisi ullamco anim in culpa in non nostrud ex aliqua ex. Nisi occaecat exercitation sint ad magna Lorem amet anim pariatur quis. Dolore consequat quis dolor ea. Aliquip sunt ex cillum tempor fugiat laboris ipsum mollit aliqua reprehenderit nulla fugiat exercitation ad.\r\n",+    "registered": "2014-03-17T05:45:50 +07:00",+    "latitude": -79.18988,+    "longitude": -95.023826,+    "tags": [+      "commodo",+      "incididunt",+      "voluptate",+      "sit",+      "mollit",+      "veniam",+      "et"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Hampton Jimenez"+      },+      {+        "id": 1,+        "name": "Lara Cline"+      },+      {+        "id": 2,+        "name": "Jennings Cantu"+      }+    ],+    "greeting": "Hello, Katie Allen! You have 3 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a90787015f61850162f",+    "index": 15,+    "guid": "d848ac32-1d24-471a-ad7e-d272f52cb5fc",+    "isActive": true,+    "balance": "$1,416.33",+    "picture": "http://placehold.it/32x32",+    "age": 38,+    "eyeColor": "brown",+    "name": "Pratt Watson",+    "gender": "male",+    "company": "HARMONEY",+    "email": "prattwatson@harmoney.com",+    "phone": "+1 (905) 557-3197",+    "address": "867 Campus Road, Lindisfarne, Northern Mariana Islands, 8349",+    "about": "Nisi mollit eu reprehenderit qui ut eu cillum cupidatat do. Et ex qui minim proident. Quis aliquip aliquip exercitation exercitation dolor aute nostrud fugiat ea. Id ullamco occaecat reprehenderit mollit occaecat ea ipsum cupidatat ea consequat fugiat.\r\n",+    "registered": "2014-11-15T05:07:03 +08:00",+    "latitude": -66.117872,+    "longitude": 52.197654,+    "tags": [+      "ullamco",+      "sunt",+      "ea",+      "veniam",+      "elit",+      "sint",+      "id"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Dale Ray"+      },+      {+        "id": 1,+        "name": "Beryl Hayes"+      },+      {+        "id": 2,+        "name": "Millie Good"+      }+    ],+    "greeting": "Hello, Pratt Watson! You have 2 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a90c4106283f0c1eed9",+    "index": 16,+    "guid": "6839b0af-5c69-45e2-b83b-381ff05034e6",+    "isActive": false,+    "balance": "$2,720.67",+    "picture": "http://placehold.it/32x32",+    "age": 31,+    "eyeColor": "blue",+    "name": "Shari Mendez",+    "gender": "female",+    "company": "INDEXIA",+    "email": "sharimendez@indexia.com",+    "phone": "+1 (895) 589-3754",+    "address": "533 Conduit Boulevard, Forestburg, Mississippi, 6873",+    "about": "Ex excepteur elit id laborum veniam dolore duis ea eiusmod minim. Consequat reprehenderit magna elit tempor voluptate esse officia minim reprehenderit consequat fugiat qui eiusmod. Et Lorem nisi voluptate tempor nulla cillum reprehenderit pariatur elit dolore occaecat laboris sunt. Anim cupidatat do veniam pariatur nostrud anim eu aliqua veniam ad cillum quis.\r\n",+    "registered": "2014-05-04T10:15:37 +07:00",+    "latitude": 31.931465,+    "longitude": 155.732674,+    "tags": [+      "enim",+      "sint",+      "est",+      "aliqua",+      "elit",+      "laboris",+      "occaecat"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Desiree Norris"+      },+      {+        "id": 1,+        "name": "Abigail Crosby"+      },+      {+        "id": 2,+        "name": "Anderson Alston"+      }+    ],+    "greeting": "Hello, Shari Mendez! You have 6 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a90474ecbf0a9c34382",+    "index": 17,+    "guid": "079bb238-0556-4ede-8243-2eaadabb3050",+    "isActive": false,+    "balance": "$2,629.01",+    "picture": "http://placehold.it/32x32",+    "age": 26,+    "eyeColor": "green",+    "name": "Hancock Rowland",+    "gender": "male",+    "company": "SOFTMICRO",+    "email": "hancockrowland@softmicro.com",+    "phone": "+1 (983) 565-2302",+    "address": "168 Dooley Street, Driftwood, North Dakota, 3607",+    "about": "Eu voluptate velit incididunt ipsum amet veniam. Lorem aliquip adipisicing incididunt laborum aliqua elit consectetur aute velit voluptate ea. Mollit irure duis consequat veniam sint nostrud est dolore sunt anim laboris fugiat. Dolor officia elit esse elit dolore. Minim non est duis culpa ullamco veniam excepteur. Veniam ullamco amet ullamco esse voluptate laboris velit.\r\n",+    "registered": "2014-09-06T04:32:40 +07:00",+    "latitude": -68.853851,+    "longitude": 135.592762,+    "tags": [+      "nulla",+      "ut",+      "magna",+      "adipisicing",+      "officia",+      "nulla",+      "cillum"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Shannon Burch"+      },+      {+        "id": 1,+        "name": "Marisol Nixon"+      },+      {+        "id": 2,+        "name": "Poole Wolfe"+      }+    ],+    "greeting": "Hello, Hancock Rowland! You have 4 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90cf708eccc9032fd0",+    "index": 18,+    "guid": "8930775e-9271-400e-b662-77ee520b3369",+    "isActive": false,+    "balance": "$1,026.68",+    "picture": "http://placehold.it/32x32",+    "age": 33,+    "eyeColor": "blue",+    "name": "Luna Rice",+    "gender": "male",+    "company": "GEEKULAR",+    "email": "lunarice@geekular.com",+    "phone": "+1 (840) 541-2996",+    "address": "417 Varick Avenue, Martell, Vermont, 2749",+    "about": "Aliqua enim ullamco do eiusmod. Ipsum incididunt minim esse veniam nisi velit. Ipsum reprehenderit velit eiusmod nisi magna dolore consectetur reprehenderit magna culpa. Commodo excepteur labore proident adipisicing in velit sit occaecat reprehenderit id.\r\n",+    "registered": "2014-10-15T05:13:55 +07:00",+    "latitude": -57.335675,+    "longitude": -173.387514,+    "tags": [+      "fugiat",+      "enim",+      "exercitation",+      "aliquip",+      "Lorem",+      "laboris",+      "deserunt"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Mclaughlin Schmidt"+      },+      {+        "id": 1,+        "name": "Munoz Key"+      },+      {+        "id": 2,+        "name": "Candace Stephenson"+      }+    ],+    "greeting": "Hello, Luna Rice! You have 2 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a90a0fd17c9e4f5d33f",+    "index": 19,+    "guid": "570f0e20-085f-4c40-9e84-b555cc4e79c8",+    "isActive": true,+    "balance": "$3,333.73",+    "picture": "http://placehold.it/32x32",+    "age": 39,+    "eyeColor": "green",+    "name": "Beverly Newton",+    "gender": "female",+    "company": "ECLIPSENT",+    "email": "beverlynewton@eclipsent.com",+    "phone": "+1 (902) 595-3072",+    "address": "903 Knight Court, Gardiner, Rhode Island, 3327",+    "about": "Labore culpa tempor nisi commodo dolore exercitation est eu. Aute exercitation duis proident deserunt culpa ullamco. Irure cillum elit ea sint dolore ipsum adipisicing anim sit. Amet incididunt labore ex fugiat mollit quis reprehenderit nisi ut excepteur proident ullamco laboris. Reprehenderit voluptate aliqua labore culpa in non magna velit nostrud amet. Minim nulla ea culpa irure. Minim ea ex nisi proident ex ex quis ipsum culpa.\r\n",+    "registered": "2014-09-23T10:25:17 +07:00",+    "latitude": 23.973865,+    "longitude": -71.604537,+    "tags": [+      "ullamco",+      "anim",+      "id",+      "pariatur",+      "est",+      "cupidatat",+      "qui"+    ],+    "friends": [+      {+        "id": 0,+        "name": "James Garner"+      },+      {+        "id": 1,+        "name": "Goff Reese"+      },+      {+        "id": 2,+        "name": "Casey Bradford"+      }+    ],+    "greeting": "Hello, Beverly Newton! You have 2 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90fd0cafc2fa513aea",+    "index": 20,+    "guid": "c0e243a3-bf20-4112-8dab-0b0aed785060",+    "isActive": true,+    "balance": "$2,290.97",+    "picture": "http://placehold.it/32x32",+    "age": 28,+    "eyeColor": "blue",+    "name": "Stokes Dodson",+    "gender": "male",+    "company": "SLAMBDA",+    "email": "stokesdodson@slambda.com",+    "phone": "+1 (994) 443-2829",+    "address": "232 Kosciusko Street, Manitou, Michigan, 2638",+    "about": "Nostrud minim cillum elit reprehenderit ut do eu. Quis deserunt enim nisi do proident quis quis voluptate elit. Sit nostrud et amet nostrud pariatur exercitation cillum ullamco laborum ullamco do id. Tempor incididunt dolore consequat ut amet in reprehenderit magna anim laboris reprehenderit tempor adipisicing.\r\n",+    "registered": "2014-05-21T16:30:03 +07:00",+    "latitude": -86.112825,+    "longitude": 56.089279,+    "tags": [+      "ea",+      "dolor",+      "dolor",+      "consequat",+      "nulla",+      "cillum",+      "voluptate"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Jodi Camacho"+      },+      {+        "id": 1,+        "name": "Michele Miles"+      },+      {+        "id": 2,+        "name": "Rene Mitchell"+      }+    ],+    "greeting": "Hello, Stokes Dodson! You have 9 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a908be67242a33d9597",+    "index": 21,+    "guid": "8ad92a47-ac1d-46ad-9dc3-d1f96526bf1d",+    "isActive": true,+    "balance": "$1,134.78",+    "picture": "http://placehold.it/32x32",+    "age": 20,+    "eyeColor": "green",+    "name": "Jody Stewart",+    "gender": "female",+    "company": "EARGO",+    "email": "jodystewart@eargo.com",+    "phone": "+1 (925) 470-3426",+    "address": "271 Norwood Avenue, Sperryville, Oklahoma, 9504",+    "about": "Ut cillum ea pariatur Lorem ad veniam ex ullamco. Exercitation culpa sit occaecat aliquip eu quis magna exercitation quis reprehenderit nostrud consequat. Exercitation magna duis culpa nulla id proident. Adipisicing sunt consequat laborum consequat id dolore aute qui. Consectetur magna irure minim ut consectetur elit qui elit voluptate sint excepteur officia.\r\n",+    "registered": "2014-04-29T09:04:08 +07:00",+    "latitude": -51.736959,+    "longitude": -37.406481,+    "tags": [+      "officia",+      "ullamco",+      "velit",+      "nulla",+      "ipsum",+      "eiusmod",+      "mollit"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Jodie Browning"+      },+      {+        "id": 1,+        "name": "Collins Gamble"+      },+      {+        "id": 2,+        "name": "Emerson Mccullough"+      }+    ],+    "greeting": "Hello, Jody Stewart! You have 4 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a905956037e359d6786",+    "index": 22,+    "guid": "730467a3-9f5d-4d41-9503-09f30222dffa",+    "isActive": false,+    "balance": "$1,821.23",+    "picture": "http://placehold.it/32x32",+    "age": 24,+    "eyeColor": "brown",+    "name": "Wall Moss",+    "gender": "male",+    "company": "BOILICON",+    "email": "wallmoss@boilicon.com",+    "phone": "+1 (865) 518-2689",+    "address": "902 Fleet Street, Cornfields, West Virginia, 9088",+    "about": "Consectetur voluptate enim est elit dolor adipisicing reprehenderit ut in proident sunt. Fugiat cillum anim ullamco qui laborum sit. Dolore qui mollit ut irure proident consequat est veniam. Qui incididunt quis duis enim quis cupidatat do nostrud dolore labore consectetur laboris consectetur. Adipisicing veniam amet officia mollit incididunt non veniam ea laboris. Enim adipisicing velit in dolore proident Lorem esse et sunt.\r\n",+    "registered": "2014-01-21T18:36:59 +08:00",+    "latitude": -70.265889,+    "longitude": -148.213176,+    "tags": [+      "ea",+      "cillum",+      "ea",+      "enim",+      "aliqua",+      "nostrud",+      "do"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Margaret Chavez"+      },+      {+        "id": 1,+        "name": "Mendez Garrison"+      },+      {+        "id": 2,+        "name": "Howard Ortiz"+      }+    ],+    "greeting": "Hello, Wall Moss! You have 9 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a9062feb6c7bfa8710d",+    "index": 23,+    "guid": "1b428a55-1b50-4bcf-b2d9-06c2e8950768",+    "isActive": true,+    "balance": "$1,045.36",+    "picture": "http://placehold.it/32x32",+    "age": 25,+    "eyeColor": "blue",+    "name": "Adele Santana",+    "gender": "female",+    "company": "EBIDCO",+    "email": "adelesantana@ebidco.com",+    "phone": "+1 (981) 528-2138",+    "address": "117 Kent Avenue, Chamizal, Iowa, 4289",+    "about": "Culpa eu nostrud mollit voluptate consequat pariatur. Qui ex laboris velit cupidatat enim. Reprehenderit commodo eu excepteur ex elit amet dolore enim. Nisi dolor pariatur enim laborum voluptate Lorem magna ipsum enim aute do sunt. Ad consectetur velit laborum magna labore Lorem occaecat quis. Exercitation ipsum velit nostrud cupidatat consequat ex non velit deserunt pariatur officia. Magna occaecat Lorem qui aliqua id aliquip laborum.\r\n",+    "registered": "2014-10-01T21:15:54 +07:00",+    "latitude": 24.339451,+    "longitude": -38.920718,+    "tags": [+      "ullamco",+      "labore",+      "magna",+      "fugiat",+      "consectetur",+      "nostrud",+      "veniam"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Sharpe Haley"+      },+      {+        "id": 1,+        "name": "Patrice Horne"+      },+      {+        "id": 2,+        "name": "Melanie Sosa"+      }+    ],+    "greeting": "Hello, Adele Santana! You have 10 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a900e0eec7c0c194713",+    "index": 24,+    "guid": "cc7114a5-89ce-4fb6-9ad8-b5886360ab42",+    "isActive": true,+    "balance": "$2,505.31",+    "picture": "http://placehold.it/32x32",+    "age": 36,+    "eyeColor": "blue",+    "name": "Lindsey Colon",+    "gender": "male",+    "company": "XYLAR",+    "email": "lindseycolon@xylar.com",+    "phone": "+1 (952) 532-3896",+    "address": "144 Poly Place, Detroit, Federated States Of Micronesia, 2004",+    "about": "Ullamco nostrud Lorem duis duis aute sunt commodo Lorem. Esse cupidatat aliqua et laborum officia et. Ea magna est eiusmod id sit ea esse consectetur qui.\r\n",+    "registered": "2014-04-05T02:52:07 +07:00",+    "latitude": -55.350071,+    "longitude": -20.446672,+    "tags": [+      "id",+      "sit",+      "cillum",+      "id",+      "exercitation",+      "reprehenderit",+      "deserunt"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Kathleen Berger"+      },+      {+        "id": 1,+        "name": "Morales Tran"+      },+      {+        "id": 2,+        "name": "Dionne Hunter"+      }+    ],+    "greeting": "Hello, Lindsey Colon! You have 3 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a90a20ec5bce57f2f55",+    "index": 25,+    "guid": "c1d3f95b-25dd-4b48-8fe8-9af5f62895e2",+    "isActive": true,+    "balance": "$1,609.28",+    "picture": "http://placehold.it/32x32",+    "age": 21,+    "eyeColor": "green",+    "name": "Langley Moore",+    "gender": "male",+    "company": "ZYTRAX",+    "email": "langleymoore@zytrax.com",+    "phone": "+1 (912) 580-3688",+    "address": "747 Abbey Court, Odessa, Kentucky, 4688",+    "about": "Eiusmod adipisicing aliquip quis voluptate qui. Enim esse culpa elit id eu dolor culpa eiusmod. Deserunt ut ea ipsum ipsum non culpa ullamco non ipsum sunt cupidatat.\r\n",+    "registered": "2014-06-28T08:29:15 +07:00",+    "latitude": 65.464748,+    "longitude": -129.638837,+    "tags": [+      "deserunt",+      "cillum",+      "sint",+      "proident",+      "ad",+      "adipisicing",+      "mollit"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Mcguire Vance"+      },+      {+        "id": 1,+        "name": "Norma Buck"+      },+      {+        "id": 2,+        "name": "Janelle Bailey"+      }+    ],+    "greeting": "Hello, Langley Moore! You have 8 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a903c9a63ef7fccce96",+    "index": 26,+    "guid": "88c32c0f-a2c6-49c2-8261-bc323895067f",+    "isActive": false,+    "balance": "$3,748.41",+    "picture": "http://placehold.it/32x32",+    "age": 26,+    "eyeColor": "blue",+    "name": "Duran Bryan",+    "gender": "male",+    "company": "BULLZONE",+    "email": "duranbryan@bullzone.com",+    "phone": "+1 (911) 598-2827",+    "address": "903 Village Road, Cressey, New Jersey, 9174",+    "about": "Do in fugiat est deserunt in sit Lorem quis reprehenderit est ut tempor. Ullamco in veniam non quis cillum consectetur id. Elit exercitation reprehenderit et amet voluptate magna voluptate minim veniam enim exercitation aute aute esse. Exercitation magna voluptate laborum ipsum cupidatat exercitation ad eu aute deserunt et. Voluptate qui aute ut elit amet excepteur nostrud officia irure do.\r\n",+    "registered": "2014-01-29T12:32:05 +08:00",+    "latitude": -51.269785,+    "longitude": 79.015891,+    "tags": [+      "consequat",+      "id",+      "exercitation",+      "incididunt",+      "adipisicing",+      "laboris",+      "laboris"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Jenny Allison"+      },+      {+        "id": 1,+        "name": "Della Hobbs"+      },+      {+        "id": 2,+        "name": "Bradshaw Owens"+      }+    ],+    "greeting": "Hello, Duran Bryan! You have 5 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a907a2f07fbb3efec0f",+    "index": 27,+    "guid": "df997a84-5ec1-4f63-acb7-140b9fed981d",+    "isActive": true,+    "balance": "$1,249.61",+    "picture": "http://placehold.it/32x32",+    "age": 28,+    "eyeColor": "blue",+    "name": "Juarez House",+    "gender": "male",+    "company": "ZUVY",+    "email": "juarezhouse@zuvy.com",+    "phone": "+1 (841) 533-3140",+    "address": "494 Atlantic Avenue, Shasta, Nevada, 8475",+    "about": "Proident eu nostrud laboris adipisicing pariatur sint ipsum exercitation velit non incididunt. Qui cupidatat reprehenderit nostrud magna sunt aute nulla aliquip velit culpa commodo fugiat laborum. Dolore consectetur esse labore magna anim exercitation dolor velit qui sunt Lorem reprehenderit amet.\r\n",+    "registered": "2014-03-16T19:21:13 +07:00",+    "latitude": 57.96486,+    "longitude": 130.460435,+    "tags": [+      "incididunt",+      "reprehenderit",+      "consequat",+      "duis",+      "veniam",+      "nulla",+      "dolor"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Keri Lara"+      },+      {+        "id": 1,+        "name": "Celina Pace"+      },+      {+        "id": 2,+        "name": "Bush Haynes"+      }+    ],+    "greeting": "Hello, Juarez House! You have 7 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a903021f42c063be598",+    "index": 28,+    "guid": "1dc6349d-4354-42a9-9f73-7bc12d5ac9c2",+    "isActive": false,+    "balance": "$1,991.33",+    "picture": "http://placehold.it/32x32",+    "age": 26,+    "eyeColor": "blue",+    "name": "Sonja Horton",+    "gender": "female",+    "company": "VIDTO",+    "email": "sonjahorton@vidto.com",+    "phone": "+1 (990) 504-2432",+    "address": "612 River Street, Carrizo, California, 5778",+    "about": "Nisi reprehenderit occaecat anim ut minim aliqua aliquip velit veniam eiusmod reprehenderit laboris ad. Duis aliqua labore cillum proident est amet. Adipisicing nostrud proident labore laborum. Elit est sint ex veniam reprehenderit. Veniam ut dolor nulla tempor voluptate sit excepteur sit ut quis.\r\n",+    "registered": "2014-10-30T05:07:28 +07:00",+    "latitude": 85.37031,+    "longitude": -122.082636,+    "tags": [+      "tempor",+      "ex",+      "nostrud",+      "ullamco",+      "ullamco",+      "nisi",+      "cillum"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Phillips Henry"+      },+      {+        "id": 1,+        "name": "Leanne Sexton"+      },+      {+        "id": 2,+        "name": "Alisha Chang"+      }+    ],+    "greeting": "Hello, Sonja Horton! You have 1 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a903a33ff6bb03e049a",+    "index": 29,+    "guid": "4ef3a65d-5407-400b-aace-62bbb8a83c6a",+    "isActive": true,+    "balance": "$2,823.03",+    "picture": "http://placehold.it/32x32",+    "age": 21,+    "eyeColor": "blue",+    "name": "Lidia Hewitt",+    "gender": "female",+    "company": "PROWASTE",+    "email": "lidiahewitt@prowaste.com",+    "phone": "+1 (909) 562-3312",+    "address": "625 Ralph Avenue, Kiskimere, Maryland, 2033",+    "about": "Eu adipisicing eiusmod laboris veniam sunt id ad eiusmod consectetur nisi. Esse nulla ipsum Lorem nisi consequat laborum. Veniam ea exercitation ullamco pariatur consectetur. Cupidatat velit excepteur ex eu sunt id. In velit reprehenderit pariatur velit. Voluptate nostrud deserunt qui mollit et tempor nostrud eiusmod adipisicing ad est laborum. Fugiat nisi sit nulla eu ad.\r\n",+    "registered": "2014-01-15T00:05:42 +08:00",+    "latitude": -50.566931,+    "longitude": -149.036259,+    "tags": [+      "in",+      "ipsum",+      "tempor",+      "magna",+      "aliqua",+      "duis",+      "incididunt"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Allie Baker"+      },+      {+        "id": 1,+        "name": "Valdez Giles"+      },+      {+        "id": 2,+        "name": "Knox Vargas"+      }+    ],+    "greeting": "Hello, Lidia Hewitt! You have 3 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a90ce7ef56e53f98790",+    "index": 30,+    "guid": "9ae34b38-098b-4de6-aa0f-90359571ddf2",+    "isActive": false,+    "balance": "$3,152.82",+    "picture": "http://placehold.it/32x32",+    "age": 38,+    "eyeColor": "brown",+    "name": "Nieves Ewing",+    "gender": "male",+    "company": "QNEKT",+    "email": "nievesewing@qnekt.com",+    "phone": "+1 (888) 460-2384",+    "address": "425 Highland Place, Hampstead, Washington, 7019",+    "about": "Ipsum aliquip ullamco labore aliquip et. Eu pariatur exercitation ex fugiat fugiat. Nulla voluptate et cupidatat deserunt mollit et incididunt. Deserunt anim qui cupidatat veniam et et dolore qui sunt tempor ipsum. In et voluptate excepteur incididunt dolore cupidatat exercitation ullamco dolore Lorem nostrud sunt nostrud. Occaecat amet ut laborum laborum aliqua est incididunt ea excepteur commodo. Ipsum et elit nisi duis pariatur occaecat.\r\n",+    "registered": "2014-06-15T18:44:29 +07:00",+    "latitude": 8.010315,+    "longitude": 69.049129,+    "tags": [+      "exercitation",+      "eiusmod",+      "voluptate",+      "ullamco",+      "consectetur",+      "reprehenderit",+      "enim"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Maggie Rodriguez"+      },+      {+        "id": 1,+        "name": "Evans Roach"+      },+      {+        "id": 2,+        "name": "Greer Mccoy"+      }+    ],+    "greeting": "Hello, Nieves Ewing! You have 7 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a9049aa4bbedefd311d",+    "index": 31,+    "guid": "471d52a6-da38-4015-8d7a-6fe215a032d3",+    "isActive": false,+    "balance": "$1,456.93",+    "picture": "http://placehold.it/32x32",+    "age": 21,+    "eyeColor": "green",+    "name": "Reyna Clay",+    "gender": "female",+    "company": "KINETICA",+    "email": "reynaclay@kinetica.com",+    "phone": "+1 (865) 425-3747",+    "address": "480 Dakota Place, Kimmell, Utah, 6677",+    "about": "Tempor esse ex amet aliqua incididunt incididunt qui commodo laborum incididunt deserunt elit id consequat. Proident duis ut ullamco non ea magna fugiat fugiat Lorem minim. Incididunt tempor nostrud proident irure laboris consectetur elit labore adipisicing fugiat nulla quis ipsum consequat. Tempor mollit pariatur irure in nisi minim magna excepteur. Aliquip et laborum cillum adipisicing consequat.\r\n",+    "registered": "2014-10-30T08:53:04 +07:00",+    "latitude": 38.437675,+    "longitude": 42.847793,+    "tags": [+      "incididunt",+      "est",+      "mollit",+      "nulla",+      "ipsum",+      "ea",+      "ea"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Glenna Macdonald"+      },+      {+        "id": 1,+        "name": "Bertha Meadows"+      },+      {+        "id": 2,+        "name": "Oneill Barnes"+      }+    ],+    "greeting": "Hello, Reyna Clay! You have 6 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a900238319b9b407aa0",+    "index": 32,+    "guid": "fa5c52e1-9a9d-44d4-8e6b-5a65221ab142",+    "isActive": true,+    "balance": "$3,113.15",+    "picture": "http://placehold.it/32x32",+    "age": 24,+    "eyeColor": "blue",+    "name": "Felicia Harris",+    "gender": "female",+    "company": "FITCORE",+    "email": "feliciaharris@fitcore.com",+    "phone": "+1 (814) 449-2730",+    "address": "641 Hazel Court, Bawcomville, Wisconsin, 4111",+    "about": "Magna in elit aute ipsum anim dolore et exercitation sunt occaecat velit. Sint tempor nulla sit laborum dolor ex irure nisi nulla veniam in labore laborum. Nulla magna excepteur exercitation aute occaecat consequat laborum Lorem velit occaecat aliqua qui. Id in aliqua dolore fugiat duis elit laborum commodo cillum ex aute minim eu.\r\n",+    "registered": "2014-04-15T21:31:39 +07:00",+    "latitude": -58.891583,+    "longitude": -162.554134,+    "tags": [+      "eiusmod",+      "dolor",+      "enim",+      "cupidatat",+      "labore",+      "sunt",+      "consectetur"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Rich Lott"+      },+      {+        "id": 1,+        "name": "Rebecca Cardenas"+      },+      {+        "id": 2,+        "name": "Jarvis Barron"+      }+    ],+    "greeting": "Hello, Felicia Harris! You have 7 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a90f03941c94fe50d08",+    "index": 33,+    "guid": "49070cf3-b2f3-445e-86ec-3ae9cddc474d",+    "isActive": false,+    "balance": "$1,105.05",+    "picture": "http://placehold.it/32x32",+    "age": 35,+    "eyeColor": "green",+    "name": "Josefina Holland",+    "gender": "female",+    "company": "GOGOL",+    "email": "josefinaholland@gogol.com",+    "phone": "+1 (809) 411-2991",+    "address": "778 McKibben Street, Whitehaven, Louisiana, 2079",+    "about": "Cillum ipsum cupidatat et dolor minim aliqua. Laborum culpa magna ullamco nulla ut labore deserunt amet sunt est esse do. Consequat aliquip eiusmod ipsum tempor. Voluptate exercitation nisi nulla dolor esse ullamco occaecat. Sint minim reprehenderit anim tempor deserunt sit exercitation. Qui proident cillum eu voluptate adipisicing velit tempor sunt sunt ipsum. Enim anim ea ea eu excepteur excepteur laboris eu.\r\n",+    "registered": "2014-09-13T12:46:51 +07:00",+    "latitude": 81.172596,+    "longitude": -151.851605,+    "tags": [+      "velit",+      "ad",+      "tempor",+      "tempor",+      "adipisicing",+      "nulla",+      "minim"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Lester Woods"+      },+      {+        "id": 1,+        "name": "Audra Shepherd"+      },+      {+        "id": 2,+        "name": "Chandler Church"+      }+    ],+    "greeting": "Hello, Josefina Holland! You have 5 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a9008a3955509cf065f",+    "index": 34,+    "guid": "4c838bee-e8b1-4c22-b8ad-e0e5d0822334",+    "isActive": false,+    "balance": "$3,032.13",+    "picture": "http://placehold.it/32x32",+    "age": 32,+    "eyeColor": "green",+    "name": "Susie Cash",+    "gender": "female",+    "company": "DOGSPA",+    "email": "susiecash@dogspa.com",+    "phone": "+1 (991) 447-2298",+    "address": "361 Quincy Street, Duryea, Florida, 7538",+    "about": "Officia eu non proident ad ea quis eu ea amet irure aliqua qui. Ea in laborum do sit. Dolor proident voluptate excepteur sit. Tempor nulla anim ex proident ipsum laboris laboris cupidatat esse dolore ex fugiat proident. Excepteur exercitation pariatur ex velit. In adipisicing eu eiusmod elit dolore ullamco nulla sit ut ut anim aliquip. Et eu eu aliquip ex aute aliquip veniam sint pariatur non incididunt in aliqua sit.\r\n",+    "registered": "2014-09-14T22:55:19 +07:00",+    "latitude": 8.46119,+    "longitude": -1.171475,+    "tags": [+      "voluptate",+      "irure",+      "ex",+      "proident",+      "nulla",+      "in",+      "esse"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Cassie Whitley"+      },+      {+        "id": 1,+        "name": "Shawna Todd"+      },+      {+        "id": 2,+        "name": "Antoinette Brooks"+      }+    ],+    "greeting": "Hello, Susie Cash! You have 1 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a9079c5bce30e9c0a68",+    "index": 35,+    "guid": "7a70e0df-c23b-435d-8d19-864cc1bd09e6",+    "isActive": false,+    "balance": "$2,523.44",+    "picture": "http://placehold.it/32x32",+    "age": 21,+    "eyeColor": "brown",+    "name": "Marquez Hartman",+    "gender": "male",+    "company": "BUZZMAKER",+    "email": "marquezhartman@buzzmaker.com",+    "phone": "+1 (880) 574-2420",+    "address": "326 Church Avenue, Yardville, Indiana, 6743",+    "about": "Ullamco reprehenderit pariatur veniam enim est nulla laboris ullamco eiusmod eu Lorem amet in amet. Incididunt consectetur reprehenderit tempor Lorem aute eiusmod laborum sunt mollit excepteur qui ea enim. Consectetur enim excepteur proident incididunt adipisicing duis est amet quis proident laboris. Consectetur Lorem consequat incididunt eiusmod mollit id amet velit quis ipsum labore. Cupidatat id minim cillum qui consequat quis non quis irure dolor voluptate ea eu.\r\n",+    "registered": "2014-06-16T07:23:30 +07:00",+    "latitude": -88.970617,+    "longitude": -28.068887,+    "tags": [+      "nostrud",+      "adipisicing",+      "quis",+      "voluptate",+      "dolore",+      "dolore",+      "reprehenderit"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Florence David"+      },+      {+        "id": 1,+        "name": "Lynn Mcleod"+      },+      {+        "id": 2,+        "name": "Catherine Abbott"+      }+    ],+    "greeting": "Hello, Marquez Hartman! You have 10 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a902bf5baf83c4fafcd",+    "index": 36,+    "guid": "56ac4022-1ed3-4298-9b70-c6060c69b13c",+    "isActive": false,+    "balance": "$1,162.32",+    "picture": "http://placehold.it/32x32",+    "age": 35,+    "eyeColor": "green",+    "name": "Bishop Gibbs",+    "gender": "male",+    "company": "MICROLUXE",+    "email": "bishopgibbs@microluxe.com",+    "phone": "+1 (808) 584-2587",+    "address": "801 Exeter Street, Trucksville, New Hampshire, 4871",+    "about": "Incididunt sit eu sunt cupidatat aute do eu eu culpa. Laborum adipisicing sit anim sit dolor labore commodo minim proident ullamco laboris sit irure. Duis dolore cupidatat ullamco anim reprehenderit aliquip incididunt sunt eu id non proident non.\r\n",+    "registered": "2014-09-10T02:39:14 +07:00",+    "latitude": -3.268064,+    "longitude": 149.311591,+    "tags": [+      "enim",+      "aute",+      "commodo",+      "consectetur",+      "Lorem",+      "est",+      "cillum"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Head Pickett"+      },+      {+        "id": 1,+        "name": "Thornton Russo"+      },+      {+        "id": 2,+        "name": "Hooper Rivera"+      }+    ],+    "greeting": "Hello, Bishop Gibbs! You have 9 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90a4406555b5bdab8f",+    "index": 37,+    "guid": "0f052dd2-283e-441e-86d7-b7f7b47224f1",+    "isActive": true,+    "balance": "$3,306.81",+    "picture": "http://placehold.it/32x32",+    "age": 34,+    "eyeColor": "brown",+    "name": "Carr Strickland",+    "gender": "male",+    "company": "EXERTA",+    "email": "carrstrickland@exerta.com",+    "phone": "+1 (862) 455-3845",+    "address": "730 Aberdeen Street, Klondike, Kansas, 4412",+    "about": "Pariatur velit qui Lorem cillum labore veniam minim. Nisi reprehenderit commodo consectetur commodo velit. Ipsum dolor ea mollit excepteur minim do excepteur. Aliquip cillum quis nostrud cillum duis eu laborum reprehenderit adipisicing adipisicing do exercitation. Laborum Lorem sunt voluptate qui quis officia nisi et elit sint veniam. Lorem velit ad do amet labore veniam consectetur.\r\n",+    "registered": "2014-01-19T10:53:58 +08:00",+    "latitude": -45.243563,+    "longitude": -17.8448,+    "tags": [+      "mollit",+      "ex",+      "qui",+      "fugiat",+      "consequat",+      "consectetur",+      "do"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Rosario Gomez"+      },+      {+        "id": 1,+        "name": "Robinson Robinson"+      },+      {+        "id": 2,+        "name": "Vicki Moreno"+      }+    ],+    "greeting": "Hello, Carr Strickland! You have 2 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a9097f6a585321fe001",+    "index": 38,+    "guid": "eee159c2-73ac-44b7-9054-d72295d3178a",+    "isActive": true,+    "balance": "$3,336.97",+    "picture": "http://placehold.it/32x32",+    "age": 30,+    "eyeColor": "brown",+    "name": "Stephens Harper",+    "gender": "male",+    "company": "KIDSTOCK",+    "email": "stephensharper@kidstock.com",+    "phone": "+1 (989) 464-3887",+    "address": "315 Hewes Street, Cuylerville, Pennsylvania, 7331",+    "about": "Et ea Lorem labore consequat laboris. Aliqua irure elit aliqua ad fugiat. Commodo cupidatat incididunt ut nostrud consequat elit ad esse magna nisi nostrud dolore eu. Minim incididunt esse ullamco esse ullamco deserunt reprehenderit commodo ut. Quis sint cupidatat excepteur et laboris incididunt id esse irure nostrud sint nostrud ut pariatur. Pariatur officia aute ullamco laboris nisi elit quis labore Lorem minim ad. Ea esse anim commodo fugiat exercitation voluptate eiusmod aliquip amet enim nulla mollit excepteur nisi.\r\n",+    "registered": "2014-03-02T06:45:25 +08:00",+    "latitude": -88.39292,+    "longitude": -121.170605,+    "tags": [+      "ullamco",+      "est",+      "mollit",+      "id",+      "commodo",+      "Lorem",+      "nostrud"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Castaneda Dillon"+      },+      {+        "id": 1,+        "name": "Morin Hodge"+      },+      {+        "id": 2,+        "name": "Vincent Lamb"+      }+    ],+    "greeting": "Hello, Stephens Harper! You have 2 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90357d07212fdefd7a",+    "index": 39,+    "guid": "160a80a4-8e5d-483a-93ed-0a37ee359f04",+    "isActive": true,+    "balance": "$1,588.64",+    "picture": "http://placehold.it/32x32",+    "age": 32,+    "eyeColor": "green",+    "name": "Espinoza May",+    "gender": "male",+    "company": "ZIDOX",+    "email": "espinozamay@zidox.com",+    "phone": "+1 (955) 420-2244",+    "address": "614 Alice Court, Vienna, Delaware, 5018",+    "about": "Pariatur voluptate culpa enim enim sunt dolor laborum consequat officia est nostrud. Ad pariatur labore reprehenderit cillum dolor consequat quis eu fugiat. Ut fugiat do dolor minim voluptate incididunt laboris magna deserunt. Culpa incididunt fugiat nostrud qui incididunt velit laboris deserunt exercitation velit.\r\n",+    "registered": "2014-09-16T06:10:45 +07:00",+    "latitude": -28.413217,+    "longitude": -76.45198,+    "tags": [+      "ullamco",+      "ut",+      "nulla",+      "consequat",+      "ea",+      "in",+      "commodo"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Virginia Obrien"+      },+      {+        "id": 1,+        "name": "Snider Hopper"+      },+      {+        "id": 2,+        "name": "Fannie Harrell"+      }+    ],+    "greeting": "Hello, Espinoza May! You have 3 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a901e753e41d1232a42",+    "index": 40,+    "guid": "7f3923e7-2fad-4210-b051-bd805e505d06",+    "isActive": false,+    "balance": "$2,512.14",+    "picture": "http://placehold.it/32x32",+    "age": 28,+    "eyeColor": "green",+    "name": "Morrison Guerrero",+    "gender": "male",+    "company": "MARQET",+    "email": "morrisonguerrero@marqet.com",+    "phone": "+1 (881) 456-3335",+    "address": "157 Huntington Street, Topaz, Georgia, 6068",+    "about": "Velit cupidatat eiusmod elit ut adipisicing. Proident pariatur consequat ullamco velit dolore do esse adipisicing non est ea. Velit non amet nisi aliqua est veniam dolor. Cillum id labore qui ut aute pariatur exercitation ut incididunt velit aute.\r\n",+    "registered": "2014-11-12T21:01:15 +08:00",+    "latitude": -43.420946,+    "longitude": 23.215515,+    "tags": [+      "sit",+      "dolore",+      "aliqua",+      "esse",+      "et",+      "cillum",+      "mollit"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Pamela Stanton"+      },+      {+        "id": 1,+        "name": "Spence Banks"+      },+      {+        "id": 2,+        "name": "Newton Cooley"+      }+    ],+    "greeting": "Hello, Morrison Guerrero! You have 4 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a9098c06d55fa03398f",+    "index": 41,+    "guid": "c17e4f56-ca2b-48fd-bbc4-aea053062ebd",+    "isActive": false,+    "balance": "$3,405.25",+    "picture": "http://placehold.it/32x32",+    "age": 26,+    "eyeColor": "brown",+    "name": "Janna Porter",+    "gender": "female",+    "company": "NIMON",+    "email": "jannaporter@nimon.com",+    "phone": "+1 (974) 475-2497",+    "address": "231 Wythe Place, Stonybrook, Alaska, 6291",+    "about": "Veniam aute dolor Lorem dolore enim eiusmod veniam esse pariatur duis nisi. Eu sit sunt in exercitation amet veniam reprehenderit voluptate proident officia. Dolore aliquip laboris veniam cupidatat non cupidatat ipsum esse labore Lorem reprehenderit ex. Amet magna do officia proident laborum laboris enim. Ea sunt quis officia minim ea in id cillum. Aute ipsum aliquip fugiat tempor proident sit pariatur duis fugiat fugiat esse cillum eiusmod laboris.\r\n",+    "registered": "2014-08-21T22:08:24 +07:00",+    "latitude": -43.52587,+    "longitude": 143.40506,+    "tags": [+      "ipsum",+      "consequat",+      "labore",+      "tempor",+      "ipsum",+      "exercitation",+      "sint"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Bray Francis"+      },+      {+        "id": 1,+        "name": "Whitley Guthrie"+      },+      {+        "id": 2,+        "name": "Christa Short"+      }+    ],+    "greeting": "Hello, Janna Porter! You have 1 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a90ddf3f9e967357339",+    "index": 42,+    "guid": "5b194b7d-c5ca-4cdd-9239-6aa6f9b28e2f",+    "isActive": false,+    "balance": "$1,601.96",+    "picture": "http://placehold.it/32x32",+    "age": 31,+    "eyeColor": "green",+    "name": "Sandoval Melton",+    "gender": "male",+    "company": "GENMEX",+    "email": "sandovalmelton@genmex.com",+    "phone": "+1 (826) 536-3504",+    "address": "726 Lester Court, Gracey, Colorado, 4092",+    "about": "Fugiat pariatur elit Lorem amet fugiat tempor. Quis sint fugiat officia deserunt voluptate fugiat aliquip reprehenderit est laborum. Ut id ullamco Lorem esse occaecat eu officia veniam velit. Aliquip nisi voluptate id aliquip qui tempor commodo occaecat velit adipisicing nisi cillum id adipisicing.\r\n",+    "registered": "2014-09-05T08:54:55 +07:00",+    "latitude": -66.159672,+    "longitude": -135.605108,+    "tags": [+      "deserunt",+      "minim",+      "nulla",+      "ea",+      "tempor",+      "reprehenderit",+      "nulla"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Veronica Goodwin"+      },+      {+        "id": 1,+        "name": "Heather Pena"+      },+      {+        "id": 2,+        "name": "Lynch Crane"+      }+    ],+    "greeting": "Hello, Sandoval Melton! You have 2 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a904d114bb57f0826af",+    "index": 43,+    "guid": "57e4715d-f7e7-4843-9844-638648315628",+    "isActive": false,+    "balance": "$1,820.21",+    "picture": "http://placehold.it/32x32",+    "age": 34,+    "eyeColor": "blue",+    "name": "Flynn Fitzgerald",+    "gender": "male",+    "company": "ORBEAN",+    "email": "flynnfitzgerald@orbean.com",+    "phone": "+1 (931) 578-2443",+    "address": "662 Degraw Street, Morgandale, Alabama, 5945",+    "about": "Nulla anim qui duis ea do consectetur eiusmod commodo. Minim sunt aliqua velit ullamco velit dolor in amet. Eiusmod Lorem sint ea ea nisi ullamco ut officia magna id adipisicing. Ut eu minim pariatur incididunt non nisi in nostrud culpa magna anim. Veniam dolor consequat cillum sunt elit.\r\n",+    "registered": "2014-06-21T08:11:23 +07:00",+    "latitude": 17.108863,+    "longitude": -21.608292,+    "tags": [+      "officia",+      "quis",+      "sint",+      "ipsum",+      "esse",+      "commodo",+      "aute"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Browning Mcdonald"+      },+      {+        "id": 1,+        "name": "Austin Lane"+      },+      {+        "id": 2,+        "name": "Blankenship Ferrell"+      }+    ],+    "greeting": "Hello, Flynn Fitzgerald! You have 6 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a903a4a0629f547f5ab",+    "index": 44,+    "guid": "1e128e33-65bf-4420-ae93-f228d030401c",+    "isActive": false,+    "balance": "$3,158.96",+    "picture": "http://placehold.it/32x32",+    "age": 25,+    "eyeColor": "brown",+    "name": "Holden Golden",+    "gender": "male",+    "company": "QABOOS",+    "email": "holdengolden@qaboos.com",+    "phone": "+1 (825) 535-2422",+    "address": "892 Vandervoort Place, Hessville, Guam, 3942",+    "about": "Non sunt cupidatat ipsum nostrud qui ad ex nostrud fugiat laboris minim. Aliquip dolore est proident aliquip in non culpa. Minim ut laboris duis proident commodo occaecat duis duis cupidatat incididunt aliqua. Ad qui anim non occaecat excepteur labore consequat Lorem.\r\n",+    "registered": "2014-09-11T00:33:16 +07:00",+    "latitude": -3.274271,+    "longitude": -151.539207,+    "tags": [+      "nostrud",+      "non",+      "sint",+      "laborum",+      "deserunt",+      "velit",+      "sit"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Barbara Sharpe"+      },+      {+        "id": 1,+        "name": "Jerri Martinez"+      },+      {+        "id": 2,+        "name": "Ronda Montoya"+      }+    ],+    "greeting": "Hello, Holden Golden! You have 3 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a9079e20c148518a60a",+    "index": 45,+    "guid": "90fa284f-6cba-42d4-8484-6edb1ffc260b",+    "isActive": false,+    "balance": "$1,739.35",+    "picture": "http://placehold.it/32x32",+    "age": 27,+    "eyeColor": "blue",+    "name": "Maxwell Hodges",+    "gender": "male",+    "company": "DOGTOWN",+    "email": "maxwellhodges@dogtown.com",+    "phone": "+1 (962) 527-3585",+    "address": "696 Baughman Place, Cucumber, South Carolina, 5454",+    "about": "Officia ea in non veniam ipsum ea magna enim velit. Enim mollit duis consectetur magna fugiat nostrud cillum duis. Voluptate qui nostrud amet ea eiusmod consectetur cupidatat fugiat in eiusmod amet esse excepteur aute. Magna qui duis ex qui irure esse amet. Quis amet ex esse aliquip mollit. Deserunt consectetur eiusmod culpa reprehenderit. Duis quis excepteur non in consectetur commodo.\r\n",+    "registered": "2014-03-20T11:37:19 +07:00",+    "latitude": 19.760315,+    "longitude": -26.731699,+    "tags": [+      "magna",+      "aute",+      "consectetur",+      "labore",+      "sunt",+      "pariatur",+      "excepteur"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Violet Vincent"+      },+      {+        "id": 1,+        "name": "West Dorsey"+      },+      {+        "id": 2,+        "name": "Concepcion Hull"+      }+    ],+    "greeting": "Hello, Maxwell Hodges! You have 3 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a903c504d648884f367",+    "index": 46,+    "guid": "7fd30275-a097-41e0-8924-252f0a14d6ba",+    "isActive": false,+    "balance": "$1,153.05",+    "picture": "http://placehold.it/32x32",+    "age": 22,+    "eyeColor": "brown",+    "name": "Chasity Day",+    "gender": "female",+    "company": "EQUITAX",+    "email": "chasityday@equitax.com",+    "phone": "+1 (900) 484-3047",+    "address": "858 Gelston Avenue, Succasunna, Arkansas, 3818",+    "about": "Est ullamco deserunt cillum enim velit velit magna qui nisi eiusmod enim officia sit voluptate. Laboris laborum elit nostrud sit in elit labore occaecat mollit Lorem. Aliquip ipsum dolore cupidatat ad anim sint non. Commodo ut fugiat ut dolor irure id velit enim in Lorem.\r\n",+    "registered": "2014-01-05T08:26:01 +08:00",+    "latitude": -34.705083,+    "longitude": -77.213981,+    "tags": [+      "consectetur",+      "velit",+      "enim",+      "excepteur",+      "ipsum",+      "non",+      "adipisicing"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Judy Trevino"+      },+      {+        "id": 1,+        "name": "Rosie Cervantes"+      },+      {+        "id": 2,+        "name": "Stella Cortez"+      }+    ],+    "greeting": "Hello, Chasity Day! You have 1 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a90ac6406c3b9606052",+    "index": 47,+    "guid": "9bcd39b8-10db-40cc-a9f0-73e8179f181b",+    "isActive": true,+    "balance": "$1,755.70",+    "picture": "http://placehold.it/32x32",+    "age": 21,+    "eyeColor": "green",+    "name": "Richards Mcfadden",+    "gender": "male",+    "company": "ZILLACOM",+    "email": "richardsmcfadden@zillacom.com",+    "phone": "+1 (883) 447-2941",+    "address": "241 Turnbull Avenue, Chesterfield, Ohio, 7594",+    "about": "Fugiat ut qui qui et Lorem ipsum do anim veniam. Sunt ex Lorem et nostrud consectetur esse nostrud qui minim ut veniam commodo. Cillum enim laboris aliquip sunt quis ut et adipisicing deserunt. Est aute amet elit non duis deserunt nisi quis excepteur do velit. Nostrud laboris ullamco sunt occaecat Lorem nostrud laboris.\r\n",+    "registered": "2014-07-04T03:08:44 +07:00",+    "latitude": -66.813737,+    "longitude": 75.337073,+    "tags": [+      "elit",+      "consequat",+      "excepteur",+      "qui",+      "deserunt",+      "sint",+      "ipsum"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Mercado Quinn"+      },+      {+        "id": 1,+        "name": "Lucia Roberts"+      },+      {+        "id": 2,+        "name": "Essie Cohen"+      }+    ],+    "greeting": "Hello, Richards Mcfadden! You have 5 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a9033409e09e42cf3f1",+    "index": 48,+    "guid": "6ea51613-14f0-42b7-97c6-eb7a5cc32226",+    "isActive": true,+    "balance": "$3,907.64",+    "picture": "http://placehold.it/32x32",+    "age": 31,+    "eyeColor": "blue",+    "name": "Sargent Harrington",+    "gender": "male",+    "company": "DIGINETIC",+    "email": "sargentharrington@diginetic.com",+    "phone": "+1 (974) 427-2742",+    "address": "883 Kermit Place, Rehrersburg, Virgin Islands, 4044",+    "about": "Officia eiusmod do ut ipsum voluptate aute mollit minim ea incididunt ipsum commodo. Qui aute commodo dolor id pariatur ullamco adipisicing duis irure reprehenderit aliquip ullamco consequat. Sunt ad non deserunt velit consectetur ipsum. Sit voluptate aliquip nisi ea pariatur proident ut eu cillum. Irure sunt ad aliqua et enim. Deserunt deserunt incididunt Lorem commodo aliqua.\r\n",+    "registered": "2014-06-16T00:32:02 +07:00",+    "latitude": -88.301682,+    "longitude": -16.268399,+    "tags": [+      "Lorem",+      "reprehenderit",+      "consequat",+      "officia",+      "velit",+      "ad",+      "ex"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Dolly Swanson"+      },+      {+        "id": 1,+        "name": "Jeannette Underwood"+      },+      {+        "id": 2,+        "name": "Ayers Wheeler"+      }+    ],+    "greeting": "Hello, Sargent Harrington! You have 3 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a9083fe43074bb5b9c8",+    "index": 49,+    "guid": "d5835fea-0882-4e20-b86e-6dd8b9534b49",+    "isActive": false,+    "balance": "$2,481.41",+    "picture": "http://placehold.it/32x32",+    "age": 22,+    "eyeColor": "green",+    "name": "Kristin Perez",+    "gender": "female",+    "company": "JAMNATION",+    "email": "kristinperez@jamnation.com",+    "phone": "+1 (917) 575-2693",+    "address": "737 Schroeders Avenue, Chemung, Nebraska, 8601",+    "about": "Voluptate id id mollit in aliquip aliquip duis cupidatat elit voluptate proident sit sunt voluptate. Irure ipsum esse duis culpa esse et do officia consequat velit commodo Lorem. Dolore incididunt proident proident officia aliqua mollit nisi exercitation consectetur. Est dolore consectetur aliqua occaecat est. Labore nostrud ipsum aliquip labore elit aliquip magna ex esse do consequat minim id. Sit sint ea ad irure voluptate occaecat duis magna. Sint ex minim irure anim sint do cillum.\r\n",+    "registered": "2014-08-12T23:35:39 +07:00",+    "latitude": 75.002428,+    "longitude": 12.266159,+    "tags": [+      "anim",+      "exercitation",+      "excepteur",+      "amet",+      "aute",+      "mollit",+      "aliquip"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Gilliam Steele"+      },+      {+        "id": 1,+        "name": "Loretta Brennan"+      },+      {+        "id": 2,+        "name": "Ratliff Norton"+      }+    ],+    "greeting": "Hello, Kristin Perez! You have 9 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90f594e03626363339",+    "index": 50,+    "guid": "bdb8284c-7b55-4791-90c0-d9e4abecd91f",+    "isActive": true,+    "balance": "$1,906.85",+    "picture": "http://placehold.it/32x32",+    "age": 33,+    "eyeColor": "green",+    "name": "Petra Scott",+    "gender": "female",+    "company": "BRAINCLIP",+    "email": "petrascott@brainclip.com",+    "phone": "+1 (942) 449-2471",+    "address": "759 Coleridge Street, Tuskahoma, Virginia, 9943",+    "about": "Consectetur magna consequat ea est. Minim veniam culpa deserunt aliqua in dolor ex aliqua occaecat reprehenderit. Proident in mollit ad est excepteur anim quis aliquip non consequat minim cillum deserunt. Occaecat sint ad et irure sit ad ad sit sint quis sunt nisi.\r\n",+    "registered": "2014-11-12T20:54:05 +08:00",+    "latitude": -64.192199,+    "longitude": -144.535209,+    "tags": [+      "cillum",+      "id",+      "aliqua",+      "reprehenderit",+      "officia",+      "aliquip",+      "in"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Holmes Rasmussen"+      },+      {+        "id": 1,+        "name": "Leach Hickman"+      },+      {+        "id": 2,+        "name": "Noelle Battle"+      }+    ],+    "greeting": "Hello, Petra Scott! You have 9 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a902f79c4cdada83391",+    "index": 51,+    "guid": "c79aaabd-212b-4dc8-b1b5-de14362b550c",+    "isActive": true,+    "balance": "$2,845.25",+    "picture": "http://placehold.it/32x32",+    "age": 32,+    "eyeColor": "brown",+    "name": "Kelli Gates",+    "gender": "female",+    "company": "MUSANPOLY",+    "email": "kelligates@musanpoly.com",+    "phone": "+1 (899) 414-3276",+    "address": "888 Elm Place, Grayhawk, Wyoming, 8032",+    "about": "Nisi excepteur laboris duis magna dolore occaecat dolor cupidatat. Ut commodo proident incididunt ea anim enim deserunt laboris occaecat anim cillum. In amet exercitation officia mollit officia quis exercitation aliqua culpa ullamco consequat excepteur aute mollit. Aliqua tempor consequat ullamco consequat qui duis tempor ex est aliqua id nulla Lorem occaecat. Et laborum culpa pariatur et ad minim. Nulla ad eiusmod pariatur velit aliquip magna duis duis tempor. Nisi ea reprehenderit aliquip magna esse occaecat consectetur ullamco esse.\r\n",+    "registered": "2014-03-01T15:11:58 +08:00",+    "latitude": 30.679655,+    "longitude": -175.200369,+    "tags": [+      "laborum",+      "non",+      "pariatur",+      "ea",+      "non",+      "proident",+      "culpa"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Coleman Mejia"+      },+      {+        "id": 1,+        "name": "Meyers Sellers"+      },+      {+        "id": 2,+        "name": "Stuart Carney"+      }+    ],+    "greeting": "Hello, Kelli Gates! You have 1 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a9040ef999266e0142c",+    "index": 52,+    "guid": "cb9bc903-5e95-4918-91ed-8485d75489fa",+    "isActive": true,+    "balance": "$1,352.24",+    "picture": "http://placehold.it/32x32",+    "age": 29,+    "eyeColor": "green",+    "name": "Maricela Conrad",+    "gender": "female",+    "company": "QUIZKA",+    "email": "maricelaconrad@quizka.com",+    "phone": "+1 (924) 592-2199",+    "address": "339 Virginia Place, Lutsen, Marshall Islands, 1479",+    "about": "Quis commodo cupidatat mollit et deserunt id ut anim nostrud nisi mollit esse commodo laborum. Cillum exercitation anim cillum esse quis consequat. Dolor laboris exercitation irure ullamco excepteur ipsum deserunt. Anim veniam dolore labore exercitation et consectetur eiusmod eu. Aliquip velit consectetur nostrud dolore.\r\n",+    "registered": "2014-06-15T22:10:22 +07:00",+    "latitude": -0.554318,+    "longitude": -65.103334,+    "tags": [+      "adipisicing",+      "irure",+      "deserunt",+      "culpa",+      "cupidatat",+      "do",+      "occaecat"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Gentry Holden"+      },+      {+        "id": 1,+        "name": "Dillard Herrera"+      },+      {+        "id": 2,+        "name": "Guzman Patton"+      }+    ],+    "greeting": "Hello, Maricela Conrad! You have 1 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a905369b0bb27f85607",+    "index": 53,+    "guid": "fa3a125f-2d10-4015-a0fd-68d7e2401a31",+    "isActive": false,+    "balance": "$2,858.28",+    "picture": "http://placehold.it/32x32",+    "age": 34,+    "eyeColor": "green",+    "name": "Simpson Cleveland",+    "gender": "male",+    "company": "ZAPPIX",+    "email": "simpsoncleveland@zappix.com",+    "phone": "+1 (808) 572-3724",+    "address": "202 Georgia Avenue, Jamestown, Texas, 6347",+    "about": "Reprehenderit elit in duis sunt quis consequat mollit ut consequat amet. Incididunt non sint culpa non mollit incididunt elit irure sint excepteur esse sit ut fugiat. Ullamco esse culpa excepteur magna laborum sunt magna ut reprehenderit aute laboris ullamco Lorem ea. Culpa ut sit amet dolor adipisicing veniam non occaecat ex ad ea. Quis nostrud sit mollit aliqua adipisicing.\r\n",+    "registered": "2014-10-27T07:51:50 +07:00",+    "latitude": -34.024135,+    "longitude": 66.799767,+    "tags": [+      "anim",+      "pariatur",+      "fugiat",+      "reprehenderit",+      "ullamco",+      "labore",+      "fugiat"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Hester Howell"+      },+      {+        "id": 1,+        "name": "Deena King"+      },+      {+        "id": 2,+        "name": "Wilkerson Fox"+      }+    ],+    "greeting": "Hello, Simpson Cleveland! You have 10 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a900271aa8695bc5cdf",+    "index": 54,+    "guid": "501e7876-6b3f-4f2e-bbaf-914e6ff23845",+    "isActive": false,+    "balance": "$2,015.78",+    "picture": "http://placehold.it/32x32",+    "age": 23,+    "eyeColor": "brown",+    "name": "Queen Mckay",+    "gender": "female",+    "company": "PROXSOFT",+    "email": "queenmckay@proxsoft.com",+    "phone": "+1 (954) 439-2402",+    "address": "749 Wakeman Place, Sidman, Massachusetts, 3345",+    "about": "Culpa irure velit cupidatat voluptate ad non anim sit sunt in esse adipisicing. Officia amet Lorem et id eiusmod non commodo deserunt irure tempor aute fugiat est cupidatat. Ut amet ad consequat nulla in in deserunt minim ipsum adipisicing eu adipisicing nisi nulla. Qui culpa sunt aute amet est consequat excepteur occaecat voluptate in do. In labore aute sit duis enim sunt qui ullamco est tempor labore aute aliquip. Sunt eu veniam in amet cillum nisi tempor quis in fugiat culpa exercitation sunt. Exercitation enim dolore sit exercitation in mollit culpa magna amet et amet.\r\n",+    "registered": "2014-05-02T07:58:52 +07:00",+    "latitude": -24.836277,+    "longitude": -108.207588,+    "tags": [+      "aliquip",+      "dolor",+      "velit",+      "non",+      "sint",+      "deserunt",+      "ut"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Maynard Shannon"+      },+      {+        "id": 1,+        "name": "Ernestine Sullivan"+      },+      {+        "id": 2,+        "name": "Sullivan Juarez"+      }+    ],+    "greeting": "Hello, Queen Mckay! You have 8 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a902c9d0c4f7379c125",+    "index": 55,+    "guid": "c13147c1-9aab-432b-9eaa-eda127454f06",+    "isActive": false,+    "balance": "$1,498.79",+    "picture": "http://placehold.it/32x32",+    "age": 21,+    "eyeColor": "green",+    "name": "Madeline Rhodes",+    "gender": "female",+    "company": "LOCAZONE",+    "email": "madelinerhodes@locazone.com",+    "phone": "+1 (817) 404-2650",+    "address": "648 Rochester Avenue, Tetherow, Arizona, 751",+    "about": "Sit velit ea enim mollit ad fugiat anim commodo minim qui sit dolor aliqua. Amet nostrud aliquip qui velit et ea reprehenderit sint ipsum quis. Ut amet aute enim proident. Et officia nostrud voluptate non tempor quis aliquip ullamco commodo consequat fugiat ut. Eiusmod sit excepteur non nostrud esse consequat nulla sunt deserunt aliqua. Commodo laborum voluptate ea eu non excepteur pariatur.\r\n",+    "registered": "2014-03-14T19:50:42 +07:00",+    "latitude": -31.489199,+    "longitude": 124.798218,+    "tags": [+      "tempor",+      "culpa",+      "pariatur",+      "irure",+      "nisi",+      "proident",+      "quis"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Hebert Kline"+      },+      {+        "id": 1,+        "name": "Daisy Cochran"+      },+      {+        "id": 2,+        "name": "Russell Gilmore"+      }+    ],+    "greeting": "Hello, Madeline Rhodes! You have 10 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a90b6d8621dab6c9126",+    "index": 56,+    "guid": "2ebcecc1-180e-46b7-bdca-9d9a6babc687",+    "isActive": false,+    "balance": "$3,190.82",+    "picture": "http://placehold.it/32x32",+    "age": 27,+    "eyeColor": "blue",+    "name": "Rasmussen Knapp",+    "gender": "male",+    "company": "BIOSPAN",+    "email": "rasmussenknapp@biospan.com",+    "phone": "+1 (860) 461-3219",+    "address": "214 Williams Court, Brady, Oregon, 5174",+    "about": "Tempor ullamco officia laborum irure cillum est culpa tempor pariatur aliqua officia incididunt tempor. Laboris adipisicing nostrud Lorem veniam enim consequat mollit nisi. Minim nulla culpa dolor esse dolore deserunt reprehenderit adipisicing aliquip.\r\n",+    "registered": "2014-08-11T17:15:51 +07:00",+    "latitude": -25.850361,+    "longitude": -36.01581,+    "tags": [+      "enim",+      "laboris",+      "dolore",+      "amet",+      "nisi",+      "in",+      "aliqua"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Letha Peters"+      },+      {+        "id": 1,+        "name": "Lavonne Mccarthy"+      },+      {+        "id": 2,+        "name": "Hensley Kim"+      }+    ],+    "greeting": "Hello, Rasmussen Knapp! You have 5 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a902e4d6731ddf406bf",+    "index": 57,+    "guid": "7a13b0b9-7cc4-477a-944f-a27eb7e74ae6",+    "isActive": false,+    "balance": "$1,711.06",+    "picture": "http://placehold.it/32x32",+    "age": 21,+    "eyeColor": "brown",+    "name": "Maryellen Russell",+    "gender": "female",+    "company": "MAXEMIA",+    "email": "maryellenrussell@maxemia.com",+    "phone": "+1 (839) 590-2573",+    "address": "514 Roosevelt Court, Sugartown, American Samoa, 6602",+    "about": "Pariatur laborum nostrud aliqua nisi cillum sunt laborum cillum minim pariatur et consectetur minim adipisicing. Exercitation non dolor id voluptate aute sint sunt. Nostrud cupidatat officia dolor cupidatat ex irure non. Fugiat laboris quis id aliqua sunt consectetur est ea. Eiusmod mollit enim Lorem cupidatat consectetur anim nostrud enim eu irure. Et eiusmod velit esse fugiat elit laboris labore voluptate ipsum. Excepteur consequat proident quis voluptate excepteur nulla in.\r\n",+    "registered": "2014-05-17T21:19:21 +07:00",+    "latitude": -15.634102,+    "longitude": -81.860909,+    "tags": [+      "ea",+      "ipsum",+      "esse",+      "veniam",+      "reprehenderit",+      "adipisicing",+      "do"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Virgie Whitaker"+      },+      {+        "id": 1,+        "name": "Earlene Ruiz"+      },+      {+        "id": 2,+        "name": "Rosa Estrada"+      }+    ],+    "greeting": "Hello, Maryellen Russell! You have 5 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a906ea1b53a8de569dd",+    "index": 58,+    "guid": "0e91224f-af4a-4cb0-a51e-5874ad140b32",+    "isActive": false,+    "balance": "$1,226.26",+    "picture": "http://placehold.it/32x32",+    "age": 21,+    "eyeColor": "blue",+    "name": "Wright Morris",+    "gender": "male",+    "company": "APPLICA",+    "email": "wrightmorris@applica.com",+    "phone": "+1 (968) 550-2677",+    "address": "630 Hornell Loop, Sparkill, Montana, 7355",+    "about": "Veniam commodo nostrud nostrud ullamco do aliquip quis dolore laboris culpa minim do est. In sunt consequat irure veniam minim duis ex commodo duis. Ullamco eu reprehenderit dolore eiusmod est exercitation nisi tempor.\r\n",+    "registered": "2014-02-05T10:33:53 +08:00",+    "latitude": -16.050009,+    "longitude": -149.883606,+    "tags": [+      "Lorem",+      "in",+      "est",+      "consequat",+      "cillum",+      "adipisicing",+      "commodo"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Washington Carson"+      },+      {+        "id": 1,+        "name": "Hines Bean"+      },+      {+        "id": 2,+        "name": "Cox Powell"+      }+    ],+    "greeting": "Hello, Wright Morris! You have 7 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a9047deb3b6b5122221",+    "index": 59,+    "guid": "f85bae2d-b72a-4f81-b596-b5338ec41578",+    "isActive": true,+    "balance": "$1,928.94",+    "picture": "http://placehold.it/32x32",+    "age": 26,+    "eyeColor": "brown",+    "name": "Agnes Guzman",+    "gender": "female",+    "company": "PEARLESSA",+    "email": "agnesguzman@pearlessa.com",+    "phone": "+1 (859) 451-3816",+    "address": "874 Duffield Street, Benson, Hawaii, 1388",+    "about": "Ad voluptate incididunt ullamco ullamco velit adipisicing culpa aute minim adipisicing non. Consectetur elit ullamco mollit ipsum aliqua in enim Lorem eiusmod nisi fugiat exercitation. Do amet pariatur deserunt ex labore exercitation non ad commodo aliquip. Nostrud magna ipsum reprehenderit qui consectetur cillum irure nostrud do quis excepteur cillum.\r\n",+    "registered": "2014-07-28T12:22:31 +07:00",+    "latitude": -89.041016,+    "longitude": -4.513498,+    "tags": [+      "eiusmod",+      "deserunt",+      "deserunt",+      "eiusmod",+      "minim",+      "nisi",+      "magna"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Kari Trujillo"+      },+      {+        "id": 1,+        "name": "Kaye Sloan"+      },+      {+        "id": 2,+        "name": "Aguilar Fields"+      }+    ],+    "greeting": "Hello, Agnes Guzman! You have 9 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a905ad56237e1e3ae99",+    "index": 60,+    "guid": "3244774e-3c8d-4c84-8ae6-d04486ce05d7",+    "isActive": false,+    "balance": "$3,045.23",+    "picture": "http://placehold.it/32x32",+    "age": 27,+    "eyeColor": "green",+    "name": "Jill Evans",+    "gender": "female",+    "company": "GOLISTIC",+    "email": "jillevans@golistic.com",+    "phone": "+1 (871) 577-2459",+    "address": "738 Putnam Avenue, Ona, South Dakota, 4749",+    "about": "Irure exercitation esse quis commodo sunt laborum esse id anim. Ut ut ad quis qui id labore ut tempor labore velit laborum pariatur. Dolore ipsum ea tempor excepteur esse cillum sit non eu sit irure adipisicing mollit. Nulla ut proident velit consequat officia magna ex duis proident aliqua amet exercitation do commodo. Nulla Lorem ad reprehenderit mollit deserunt veniam qui duis enim enim consectetur do.\r\n",+    "registered": "2014-05-05T11:50:14 +07:00",+    "latitude": -56.941561,+    "longitude": -169.158104,+    "tags": [+      "consequat",+      "minim",+      "minim",+      "laborum",+      "aute",+      "nisi",+      "aliqua"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Compton Becker"+      },+      {+        "id": 1,+        "name": "Conner Wall"+      },+      {+        "id": 2,+        "name": "Bright Harvey"+      }+    ],+    "greeting": "Hello, Jill Evans! You have 2 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a90f530d8bc9e6faf86",+    "index": 61,+    "guid": "a983ed9d-fb1b-48ee-94b7-54b33634ccbc",+    "isActive": false,+    "balance": "$3,091.12",+    "picture": "http://placehold.it/32x32",+    "age": 21,+    "eyeColor": "brown",+    "name": "Susana Berry",+    "gender": "female",+    "company": "POWERNET",+    "email": "susanaberry@powernet.com",+    "phone": "+1 (879) 454-3173",+    "address": "141 Cambridge Place, Florence, District Of Columbia, 6760",+    "about": "Magna occaecat laborum voluptate excepteur. Proident esse voluptate enim est exercitation elit voluptate minim ex. Velit officia cupidatat sit quis reprehenderit minim voluptate et nulla cupidatat duis mollit. Aute incididunt id magna nostrud cupidatat qui sit. Enim anim enim excepteur est sunt non enim proident sit non deserunt et veniam culpa. Ullamco irure ullamco id esse exercitation anim et fugiat labore. Do enim exercitation deserunt sint consectetur occaecat commodo commodo elit.\r\n",+    "registered": "2014-06-08T14:24:28 +07:00",+    "latitude": 75.590221,+    "longitude": -127.23641,+    "tags": [+      "esse",+      "anim",+      "est",+      "anim",+      "esse",+      "adipisicing",+      "mollit"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Helene Holcomb"+      },+      {+        "id": 1,+        "name": "Kramer Gregory"+      },+      {+        "id": 2,+        "name": "Fitzgerald Graves"+      }+    ],+    "greeting": "Hello, Susana Berry! You have 1 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a90e6f9c07f205357ec",+    "index": 62,+    "guid": "26cecce2-1202-4703-8bf5-8ff431336b37",+    "isActive": false,+    "balance": "$3,437.96",+    "picture": "http://placehold.it/32x32",+    "age": 33,+    "eyeColor": "blue",+    "name": "Weeks Wallace",+    "gender": "male",+    "company": "SUREMAX",+    "email": "weekswallace@suremax.com",+    "phone": "+1 (946) 528-2733",+    "address": "219 Dekalb Avenue, Hasty, North Carolina, 7278",+    "about": "Magna dolor ex dolor eiusmod sint laboris consequat non magna ipsum. Nostrud qui aute ut consectetur in amet deserunt aliqua voluptate. In velit nisi voluptate amet consequat. Tempor deserunt velit ut aliqua duis cupidatat laboris amet in ipsum aliquip adipisicing. Quis anim cillum labore ipsum commodo deserunt mollit tempor tempor pariatur dolore. Irure aute ipsum do occaecat laborum eiusmod ad proident exercitation elit cillum do.\r\n",+    "registered": "2014-07-17T22:33:55 +07:00",+    "latitude": -22.986103,+    "longitude": -110.848014,+    "tags": [+      "ut",+      "irure",+      "elit",+      "quis",+      "ut",+      "fugiat",+      "sunt"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Colon Tyler"+      },+      {+        "id": 1,+        "name": "Dianne Lucas"+      },+      {+        "id": 2,+        "name": "Mathews Acosta"+      }+    ],+    "greeting": "Hello, Weeks Wallace! You have 8 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a9034039bde61fe5fdb",+    "index": 63,+    "guid": "cad69671-eb0a-4128-8024-9a0f469cf1f8",+    "isActive": true,+    "balance": "$2,503.03",+    "picture": "http://placehold.it/32x32",+    "age": 21,+    "eyeColor": "green",+    "name": "Watson Tucker",+    "gender": "male",+    "company": "IMKAN",+    "email": "watsontucker@imkan.com",+    "phone": "+1 (841) 499-3837",+    "address": "297 Falmouth Street, Woodlands, Tennessee, 2227",+    "about": "Do aliquip laboris voluptate aliqua duis duis qui. Lorem officia consectetur et in nulla do aliquip occaecat labore officia sunt ipsum ad consequat. Culpa voluptate adipisicing dolore do velit ea.\r\n",+    "registered": "2014-09-16T17:39:32 +07:00",+    "latitude": 76.336221,+    "longitude": -154.036452,+    "tags": [+      "veniam",+      "non",+      "aliquip",+      "ex",+      "esse",+      "sint",+      "voluptate"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Lolita Finch"+      },+      {+        "id": 1,+        "name": "Gilmore Solis"+      },+      {+        "id": 2,+        "name": "Faith Gallegos"+      }+    ],+    "greeting": "Hello, Watson Tucker! You have 9 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a90780b195513995278",+    "index": 64,+    "guid": "625810dc-6353-4387-862c-e71ed6115966",+    "isActive": false,+    "balance": "$2,428.28",+    "picture": "http://placehold.it/32x32",+    "age": 33,+    "eyeColor": "green",+    "name": "Levy Ortega",+    "gender": "male",+    "company": "FIBEROX",+    "email": "levyortega@fiberox.com",+    "phone": "+1 (819) 514-2393",+    "address": "485 Dunne Place, Sena, Idaho, 1583",+    "about": "Exercitation reprehenderit voluptate aliqua laborum dolore consequat pariatur ipsum occaecat culpa et dolore voluptate. Laborum ut adipisicing exercitation irure ipsum ea. Occaecat aute officia et qui id elit adipisicing consectetur. Laborum ullamco pariatur eiusmod dolore aliqua ea nostrud. Occaecat ullamco nostrud deserunt ea officia. Deserunt incididunt et laboris veniam laboris adipisicing culpa laborum occaecat culpa sit labore. Irure in ipsum exercitation ea laboris culpa.\r\n",+    "registered": "2014-06-19T01:29:28 +07:00",+    "latitude": -66.489978,+    "longitude": 35.142253,+    "tags": [+      "consequat",+      "ut",+      "ipsum",+      "ullamco",+      "anim",+      "irure",+      "consectetur"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Nellie Webb"+      },+      {+        "id": 1,+        "name": "Sheri Roy"+      },+      {+        "id": 2,+        "name": "Elizabeth Cooke"+      }+    ],+    "greeting": "Hello, Levy Ortega! You have 3 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a901df67d5f687df4ab",+    "index": 65,+    "guid": "73d49a86-1caa-4ef5-b637-aef68b215e96",+    "isActive": true,+    "balance": "$3,990.52",+    "picture": "http://placehold.it/32x32",+    "age": 24,+    "eyeColor": "blue",+    "name": "Webster Conway",+    "gender": "male",+    "company": "PRISMATIC",+    "email": "websterconway@prismatic.com",+    "phone": "+1 (857) 462-3648",+    "address": "557 Dekoven Court, Sandston, Palau, 8537",+    "about": "Fugiat non velit fugiat sint minim magna occaecat deserunt enim adipisicing excepteur amet. Esse incididunt mollit nisi ut id consectetur qui laborum consequat et occaecat. Tempor cupidatat enim sunt cillum. Laborum consequat Lorem enim fugiat id voluptate incididunt aliquip eu esse dolor duis ad. Officia dolore duis nisi veniam ut ut dolore fugiat deserunt aute consectetur. Excepteur voluptate mollit velit duis ullamco voluptate ullamco.\r\n",+    "registered": "2014-04-30T14:39:28 +07:00",+    "latitude": -67.082095,+    "longitude": 38.979656,+    "tags": [+      "amet",+      "aliquip",+      "ea",+      "culpa",+      "incididunt",+      "dolor",+      "fugiat"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Sharp Ramirez"+      },+      {+        "id": 1,+        "name": "Leona Campbell"+      },+      {+        "id": 2,+        "name": "Hyde Reyes"+      }+    ],+    "greeting": "Hello, Webster Conway! You have 3 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a90f7b820f93d4df443",+    "index": 66,+    "guid": "1a306b16-3a38-426c-9254-22dd7264c108",+    "isActive": true,+    "balance": "$2,194.31",+    "picture": "http://placehold.it/32x32",+    "age": 33,+    "eyeColor": "brown",+    "name": "Hill Lancaster",+    "gender": "male",+    "company": "UNIWORLD",+    "email": "hilllancaster@uniworld.com",+    "phone": "+1 (836) 462-3865",+    "address": "786 Dictum Court, Roulette, Minnesota, 3625",+    "about": "Adipisicing ea commodo et nulla ullamco fugiat do magna occaecat consectetur cupidatat nisi fugiat consequat. Velit veniam eu cupidatat adipisicing culpa non non velit amet labore. Dolore dolor enim eiusmod ipsum excepteur non sint ullamco irure laboris nulla id.\r\n",+    "registered": "2014-03-09T12:51:14 +07:00",+    "latitude": -65.924237,+    "longitude": 161.521943,+    "tags": [+      "amet",+      "id",+      "aliquip",+      "aliquip",+      "exercitation",+      "excepteur",+      "cupidatat"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Morrow Mullen"+      },+      {+        "id": 1,+        "name": "Calderon Campos"+      },+      {+        "id": 2,+        "name": "Janis Ramsey"+      }+    ],+    "greeting": "Hello, Hill Lancaster! You have 10 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a906c7f0305536c965c",+    "index": 67,+    "guid": "d1dadd20-7ced-447d-ba72-61e886f4195e",+    "isActive": true,+    "balance": "$3,014.64",+    "picture": "http://placehold.it/32x32",+    "age": 25,+    "eyeColor": "brown",+    "name": "Fletcher Pearson",+    "gender": "male",+    "company": "QOT",+    "email": "fletcherpearson@qot.com",+    "phone": "+1 (930) 439-2213",+    "address": "779 Middagh Street, Crayne, Illinois, 2229",+    "about": "Nulla sit officia velit dolore eiusmod veniam anim laborum Lorem anim et enim irure. Quis labore ad proident occaecat dolore tempor occaecat dolor dolore aliqua laboris esse enim. Dolore tempor cupidatat aute labore aliqua nulla non est.\r\n",+    "registered": "2014-04-25T09:09:10 +07:00",+    "latitude": -2.329892,+    "longitude": 135.411164,+    "tags": [+      "magna",+      "ipsum",+      "tempor",+      "velit",+      "esse",+      "magna",+      "nisi"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Tonya Ashley"+      },+      {+        "id": 1,+        "name": "Latisha Burris"+      },+      {+        "id": 2,+        "name": "Tracey Rowe"+      }+    ],+    "greeting": "Hello, Fletcher Pearson! You have 9 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a9057e5269ab81d8e18",+    "index": 68,+    "guid": "4723bd56-76cb-4a30-bdf5-8b4c4c9805aa",+    "isActive": false,+    "balance": "$3,114.82",+    "picture": "http://placehold.it/32x32",+    "age": 23,+    "eyeColor": "blue",+    "name": "Odessa Lawson",+    "gender": "female",+    "company": "JOVIOLD",+    "email": "odessalawson@joviold.com",+    "phone": "+1 (921) 400-3177",+    "address": "443 Ryder Avenue, Camino, Missouri, 487",+    "about": "Labore aliquip nulla est eiusmod amet Lorem duis fugiat duis do excepteur voluptate in non. Consequat nisi reprehenderit ullamco eu sit id consectetur commodo culpa consequat dolore aliqua. Amet nulla ut in laboris dolor do ad laboris eu commodo qui dolore pariatur. Et qui cillum anim dolore ipsum excepteur id laboris minim dolor.\r\n",+    "registered": "2014-03-26T22:07:14 +07:00",+    "latitude": 18.291259,+    "longitude": -86.22914,+    "tags": [+      "nisi",+      "ut",+      "dolore",+      "ullamco",+      "cupidatat",+      "nulla",+      "laborum"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Vonda Cote"+      },+      {+        "id": 1,+        "name": "Lewis Gonzales"+      },+      {+        "id": 2,+        "name": "Jacqueline Richmond"+      }+    ],+    "greeting": "Hello, Odessa Lawson! You have 4 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90f748a6195f1c2b42",+    "index": 69,+    "guid": "697c2775-d924-436d-ba9f-e21bd4fa0f73",+    "isActive": false,+    "balance": "$1,269.09",+    "picture": "http://placehold.it/32x32",+    "age": 40,+    "eyeColor": "green",+    "name": "Selena Nieves",+    "gender": "female",+    "company": "INTRAWEAR",+    "email": "selenanieves@intrawear.com",+    "phone": "+1 (862) 476-3196",+    "address": "101 Harkness Avenue, Worton, Connecticut, 400",+    "about": "Sunt aliquip exercitation dolor dolore nostrud anim enim anim est officia. Minim ea Lorem ullamco reprehenderit officia reprehenderit ex ex reprehenderit. Qui nulla ullamco consequat minim exercitation. Veniam id eu pariatur ut veniam dolore aliquip qui aliqua incididunt ut.\r\n",+    "registered": "2014-03-07T15:11:53 +08:00",+    "latitude": -43.588256,+    "longitude": 43.731157,+    "tags": [+      "duis",+      "duis",+      "reprehenderit",+      "ad",+      "magna",+      "excepteur",+      "officia"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Rutledge Hines"+      },+      {+        "id": 1,+        "name": "Carrillo Johnson"+      },+      {+        "id": 2,+        "name": "Morgan Parks"+      }+    ],+    "greeting": "Hello, Selena Nieves! You have 9 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a90628ac7e339a407a5",+    "index": 70,+    "guid": "6ddd302b-fd62-47f9-aa8a-9fb2c35172c5",+    "isActive": true,+    "balance": "$2,185.43",+    "picture": "http://placehold.it/32x32",+    "age": 34,+    "eyeColor": "blue",+    "name": "Georgia Moran",+    "gender": "female",+    "company": "EARTHMARK",+    "email": "georgiamoran@earthmark.com",+    "phone": "+1 (970) 439-3884",+    "address": "848 Fay Court, Hayes, Maine, 7603",+    "about": "Ea do et excepteur dolor cupidatat nisi. Quis deserunt cupidatat duis enim eu deserunt anim enim. Cillum consectetur occaecat exercitation occaecat duis dolor voluptate officia exercitation irure. Occaecat magna proident enim exercitation veniam. Est commodo ullamco minim esse enim commodo ea commodo nulla et voluptate aliqua anim. Quis magna commodo nulla quis sit duis id et ex id ea dolore anim.\r\n",+    "registered": "2014-04-22T10:10:46 +07:00",+    "latitude": -78.673083,+    "longitude": 167.271688,+    "tags": [+      "non",+      "labore",+      "adipisicing",+      "cupidatat",+      "cupidatat",+      "quis",+      "aliquip"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Andrews Love"+      },+      {+        "id": 1,+        "name": "Brianna Yates"+      },+      {+        "id": 2,+        "name": "Jennifer Bass"+      }+    ],+    "greeting": "Hello, Georgia Moran! You have 10 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a901ec4ee748cbbc125",+    "index": 71,+    "guid": "0e5fc95f-e605-4baa-bc36-b2f795c42e03",+    "isActive": false,+    "balance": "$2,256.63",+    "picture": "http://placehold.it/32x32",+    "age": 22,+    "eyeColor": "blue",+    "name": "House Sargent",+    "gender": "male",+    "company": "FANFARE",+    "email": "housesargent@fanfare.com",+    "phone": "+1 (877) 416-3590",+    "address": "961 Olive Street, Sultana, Puerto Rico, 3764",+    "about": "Cillum minim aliquip sit nulla aute commodo pariatur reprehenderit aliqua laboris in sint velit. Qui irure ut nostrud elit aliqua reprehenderit tempor quis laboris. Voluptate enim eiusmod laborum est excepteur enim est. Veniam non magna ea proident deserunt ipsum Lorem adipisicing velit eiusmod. Culpa id ea non ex ad.\r\n",+    "registered": "2014-04-27T09:04:21 +07:00",+    "latitude": -54.902834,+    "longitude": 147.773359,+    "tags": [+      "cupidatat",+      "voluptate",+      "dolor",+      "excepteur",+      "exercitation",+      "id",+      "in"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Francis Alford"+      },+      {+        "id": 1,+        "name": "Berta Sparks"+      },+      {+        "id": 2,+        "name": "Laura Hahn"+      }+    ],+    "greeting": "Hello, House Sargent! You have 9 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90b51db1c7d287288e",+    "index": 72,+    "guid": "574450df-adad-4071-b642-0971f5f7928e",+    "isActive": true,+    "balance": "$2,263.30",+    "picture": "http://placehold.it/32x32",+    "age": 38,+    "eyeColor": "green",+    "name": "Roseann Lambert",+    "gender": "female",+    "company": "ROUGHIES",+    "email": "roseannlambert@roughies.com",+    "phone": "+1 (908) 551-2196",+    "address": "142 Girard Street, Levant, New Mexico, 2920",+    "about": "Aliquip ea elit anim aliqua in ea. Ex nisi nulla veniam amet. Occaecat pariatur qui consectetur consectetur anim ut anim cillum consectetur adipisicing pariatur. Cillum quis aliquip ad do in fugiat minim et ad labore sit ut aliqua cillum. Elit enim laboris incididunt laboris. Est in ullamco laboris deserunt aliqua veniam. Aute cillum culpa do dolore.\r\n",+    "registered": "2014-04-29T13:57:00 +07:00",+    "latitude": 0.812689,+    "longitude": 127.517728,+    "tags": [+      "veniam",+      "quis",+      "do",+      "aliqua",+      "deserunt",+      "id",+      "ut"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Amelia Dunlap"+      },+      {+        "id": 1,+        "name": "Benson Huff"+      },+      {+        "id": 2,+        "name": "Aimee Fisher"+      }+    ],+    "greeting": "Hello, Roseann Lambert! You have 6 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a90285dfadf5a8ab1db",+    "index": 73,+    "guid": "cf2c0b72-e90e-43d1-a5ee-ff85e28272ec",+    "isActive": false,+    "balance": "$3,053.88",+    "picture": "http://placehold.it/32x32",+    "age": 20,+    "eyeColor": "brown",+    "name": "Althea Burgess",+    "gender": "female",+    "company": "RODEMCO",+    "email": "altheaburgess@rodemco.com",+    "phone": "+1 (946) 406-2326",+    "address": "127 Fleet Place, Northridge, Northern Mariana Islands, 2106",+    "about": "Ea aliquip mollit consectetur non sunt. Deserunt adipisicing sint non excepteur veniam. Sunt eu ad Lorem qui laborum labore. Dolor ut esse veniam dolor officia. Minim occaecat labore voluptate exercitation fugiat eu labore non dolor ipsum do magna qui incididunt.\r\n",+    "registered": "2014-03-16T23:19:14 +07:00",+    "latitude": -63.483213,+    "longitude": 118.593144,+    "tags": [+      "amet",+      "veniam",+      "consequat",+      "deserunt",+      "magna",+      "ea",+      "aliqua"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Delacruz Pollard"+      },+      {+        "id": 1,+        "name": "Kelly Washington"+      },+      {+        "id": 2,+        "name": "Irma Freeman"+      }+    ],+    "greeting": "Hello, Althea Burgess! You have 10 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a902435e7e0219ce954",+    "index": 74,+    "guid": "e3079855-52fe-494a-bb64-cf88934d1f84",+    "isActive": false,+    "balance": "$1,615.23",+    "picture": "http://placehold.it/32x32",+    "age": 28,+    "eyeColor": "brown",+    "name": "Ursula French",+    "gender": "female",+    "company": "KLUGGER",+    "email": "ursulafrench@klugger.com",+    "phone": "+1 (803) 454-3759",+    "address": "768 Bancroft Place, Layhill, Mississippi, 3591",+    "about": "Anim proident aliquip non amet. Dolore occaecat non irure cillum excepteur eu officia irure ea enim incididunt. Adipisicing ad esse voluptate duis dolor irure. Non sunt cillum est exercitation labore cillum minim consequat occaecat est aliqua. Ex adipisicing consequat aliqua pariatur est elit deserunt. Proident fugiat aliquip culpa magna ut deserunt incididunt eu enim esse fugiat eu consequat amet. Ad sit do Lorem aliquip enim ea magna.\r\n",+    "registered": "2014-08-01T00:12:36 +07:00",+    "latitude": -31.044528,+    "longitude": -131.432956,+    "tags": [+      "consequat",+      "enim",+      "nisi",+      "cupidatat",+      "est",+      "sint",+      "nostrud"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Mai Hudson"+      },+      {+        "id": 1,+        "name": "Erickson Perkins"+      },+      {+        "id": 2,+        "name": "Cleveland Armstrong"+      }+    ],+    "greeting": "Hello, Ursula French! You have 8 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a906ae58205e4d302e3",+    "index": 75,+    "guid": "f8b68baa-b922-4a5f-9d4a-fa7d9eee543c",+    "isActive": true,+    "balance": "$1,500.61",+    "picture": "http://placehold.it/32x32",+    "age": 29,+    "eyeColor": "blue",+    "name": "Lee Kelley",+    "gender": "male",+    "company": "INQUALA",+    "email": "leekelley@inquala.com",+    "phone": "+1 (947) 551-3499",+    "address": "973 Bayard Street, Orovada, North Dakota, 8499",+    "about": "Ullamco labore fugiat in minim duis duis reprehenderit deserunt eiusmod nostrud cillum laborum. Incididunt occaecat duis esse consectetur voluptate velit pariatur. Pariatur et qui sint anim sunt fugiat ut proident nulla fugiat. Nostrud ea sunt occaecat nulla qui id cupidatat proident ut.\r\n",+    "registered": "2014-11-01T01:18:21 +07:00",+    "latitude": -59.393379,+    "longitude": -110.276914,+    "tags": [+      "sit",+      "magna",+      "in",+      "consequat",+      "excepteur",+      "consequat",+      "Lorem"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Walton Daniels"+      },+      {+        "id": 1,+        "name": "Hammond Robbins"+      },+      {+        "id": 2,+        "name": "Evangelina Chaney"+      }+    ],+    "greeting": "Hello, Lee Kelley! You have 8 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a902a956e2130c70b9c",+    "index": 76,+    "guid": "3f62418d-23f0-4ed2-a76f-51645437223b",+    "isActive": false,+    "balance": "$2,477.19",+    "picture": "http://placehold.it/32x32",+    "age": 28,+    "eyeColor": "green",+    "name": "Miles Poole",+    "gender": "male",+    "company": "YOGASM",+    "email": "milespoole@yogasm.com",+    "phone": "+1 (866) 412-2294",+    "address": "744 Sutton Street, Richford, Vermont, 7668",+    "about": "Nostrud reprehenderit officia officia ex proident sint nulla sint quis duis laborum duis. Anim adipisicing officia officia anim aliquip incididunt culpa duis sint do ullamco ex consectetur. Commodo et tempor eu consequat laboris tempor ipsum magna elit labore consectetur excepteur.\r\n",+    "registered": "2014-07-20T11:37:28 +07:00",+    "latitude": 23.618997,+    "longitude": -80.142779,+    "tags": [+      "magna",+      "exercitation",+      "Lorem",+      "nostrud",+      "pariatur",+      "ut",+      "laborum"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Claudine Gill"+      },+      {+        "id": 1,+        "name": "Huff Kramer"+      },+      {+        "id": 2,+        "name": "Bryan Doyle"+      }+    ],+    "greeting": "Hello, Miles Poole! You have 10 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90357b2420c0dc0895",+    "index": 77,+    "guid": "ef7aa63d-53b9-4e85-ac36-4cfb1afd214a",+    "isActive": true,+    "balance": "$1,105.70",+    "picture": "http://placehold.it/32x32",+    "age": 39,+    "eyeColor": "green",+    "name": "Cohen Clemons",+    "gender": "male",+    "company": "EARBANG",+    "email": "cohenclemons@earbang.com",+    "phone": "+1 (936) 599-3992",+    "address": "952 Paerdegat Avenue, Frizzleburg, Rhode Island, 7905",+    "about": "Consequat exercitation fugiat ut labore. Ad magna sunt dolore magna nulla. Laborum deserunt magna eu incididunt magna occaecat ut sit aliqua nisi culpa dolor ullamco esse. Enim adipisicing proident proident enim qui est ad non. Ex proident sit irure cillum in nostrud veniam amet excepteur voluptate commodo eu eiusmod ullamco.\r\n",+    "registered": "2014-06-15T00:23:03 +07:00",+    "latitude": 78.28454,+    "longitude": 64.170517,+    "tags": [+      "non",+      "ex",+      "sit",+      "ipsum",+      "esse",+      "enim",+      "minim"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Sanchez Wolf"+      },+      {+        "id": 1,+        "name": "Trudy Melendez"+      },+      {+        "id": 2,+        "name": "Magdalena Hatfield"+      }+    ],+    "greeting": "Hello, Cohen Clemons! You have 3 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a90ae5beac7dec2ba78",+    "index": 78,+    "guid": "1bccbb43-2801-4c99-a646-001622f3653c",+    "isActive": false,+    "balance": "$1,246.07",+    "picture": "http://placehold.it/32x32",+    "age": 35,+    "eyeColor": "blue",+    "name": "Evelyn Combs",+    "gender": "female",+    "company": "BITENDREX",+    "email": "evelyncombs@bitendrex.com",+    "phone": "+1 (937) 463-3648",+    "address": "498 Beayer Place, Caln, Michigan, 637",+    "about": "Laboris ipsum ea est nisi exercitation adipisicing commodo ea ipsum tempor. Laboris duis sit aliquip reprehenderit id cillum officia. Tempor velit reprehenderit adipisicing dolor ut qui id magna consequat. Fugiat proident sunt sit reprehenderit duis. Eu ipsum adipisicing eu magna cupidatat mollit esse.\r\n",+    "registered": "2014-06-14T20:28:41 +07:00",+    "latitude": 13.771679,+    "longitude": 58.844141,+    "tags": [+      "ex",+      "irure",+      "quis",+      "velit",+      "deserunt",+      "mollit",+      "irure"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Melton Wright"+      },+      {+        "id": 1,+        "name": "Letitia Osborn"+      },+      {+        "id": 2,+        "name": "Delgado Hebert"+      }+    ],+    "greeting": "Hello, Evelyn Combs! You have 7 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90a58e6a9e15665417",+    "index": 79,+    "guid": "6c039c25-e902-4121-9319-aa5e25f09b90",+    "isActive": true,+    "balance": "$1,839.17",+    "picture": "http://placehold.it/32x32",+    "age": 27,+    "eyeColor": "blue",+    "name": "Lakeisha Glover",+    "gender": "female",+    "company": "REALMO",+    "email": "lakeishaglover@realmo.com",+    "phone": "+1 (962) 444-3749",+    "address": "259 Homecrest Court, Villarreal, Oklahoma, 5463",+    "about": "Lorem cupidatat non elit mollit commodo tempor dolore sint qui officia fugiat mollit ex. Ea commodo minim labore commodo commodo sint occaecat eu aute est excepteur labore eiusmod ut. Fugiat voluptate laborum mollit aliqua ea. Ut fugiat culpa do velit non nisi voluptate exercitation. Duis elit velit adipisicing esse.\r\n",+    "registered": "2014-10-07T01:42:52 +07:00",+    "latitude": 7.777536,+    "longitude": -148.148716,+    "tags": [+      "officia",+      "Lorem",+      "eu",+      "eiusmod",+      "mollit",+      "duis",+      "exercitation"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Hodge Snider"+      },+      {+        "id": 1,+        "name": "Maritza Gilliam"+      },+      {+        "id": 2,+        "name": "Barbra Harding"+      }+    ],+    "greeting": "Hello, Lakeisha Glover! You have 5 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a904e60ffa71e64c330",+    "index": 80,+    "guid": "764804ce-2ded-4ab7-aef7-cc638d74a1f6",+    "isActive": true,+    "balance": "$3,096.79",+    "picture": "http://placehold.it/32x32",+    "age": 36,+    "eyeColor": "green",+    "name": "Hall Woodard",+    "gender": "male",+    "company": "HOTCAKES",+    "email": "hallwoodard@hotcakes.com",+    "phone": "+1 (923) 579-2251",+    "address": "678 Johnson Avenue, Nescatunga, West Virginia, 5718",+    "about": "Nulla Lorem proident incididunt nulla amet. Consectetur ea fugiat est adipisicing nisi exercitation ea irure. Id nisi anim culpa incididunt ea laborum. Non est aute anim deserunt ad cupidatat.\r\n",+    "registered": "2014-07-25T16:17:12 +07:00",+    "latitude": -72.300952,+    "longitude": 149.908938,+    "tags": [+      "magna",+      "fugiat",+      "laboris",+      "nulla",+      "tempor",+      "consequat",+      "officia"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Tessa Mccray"+      },+      {+        "id": 1,+        "name": "Hendricks Larson"+      },+      {+        "id": 2,+        "name": "Cecilia Mooney"+      }+    ],+    "greeting": "Hello, Hall Woodard! You have 1 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a9095c0ba3039bba2a0",+    "index": 81,+    "guid": "477bf131-1833-4ceb-a02d-367662e8a0ad",+    "isActive": true,+    "balance": "$3,975.04",+    "picture": "http://placehold.it/32x32",+    "age": 20,+    "eyeColor": "brown",+    "name": "Mccray Holman",+    "gender": "male",+    "company": "NETROPIC",+    "email": "mccrayholman@netropic.com",+    "phone": "+1 (888) 582-2650",+    "address": "930 Alton Place, Stouchsburg, Iowa, 8307",+    "about": "Officia laboris laboris ea magna nulla exercitation ullamco est magna proident cupidatat dolor. Et dolore fugiat nostrud elit in Lorem cillum eu incididunt ullamco irure eiusmod voluptate aliqua. Sint velit veniam nisi in amet incididunt ex et voluptate. Amet laborum sint fugiat nulla dolor adipisicing eiusmod ea. Labore amet nisi et pariatur duis non aliquip ea pariatur sit. Est eu dolore consequat ex quis qui adipisicing minim ea. Proident eiusmod enim irure cillum eu do laborum do voluptate occaecat id consectetur irure dolor.\r\n",+    "registered": "2014-07-15T14:32:20 +07:00",+    "latitude": 69.28999,+    "longitude": 20.24303,+    "tags": [+      "deserunt",+      "labore",+      "voluptate",+      "sint",+      "occaecat",+      "non",+      "sunt"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Ashley Mcintosh"+      },+      {+        "id": 1,+        "name": "Augusta Barr"+      },+      {+        "id": 2,+        "name": "Page Wiley"+      }+    ],+    "greeting": "Hello, Mccray Holman! You have 8 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a908bfd72d2f3594530",+    "index": 82,+    "guid": "e7128ea4-81e3-4888-ba11-4f3670e473ea",+    "isActive": false,+    "balance": "$3,306.06",+    "picture": "http://placehold.it/32x32",+    "age": 22,+    "eyeColor": "green",+    "name": "Sherri Villarreal",+    "gender": "female",+    "company": "TERRAGEN",+    "email": "sherrivillarreal@terragen.com",+    "phone": "+1 (881) 513-2832",+    "address": "922 Surf Avenue, Oberlin, Federated States Of Micronesia, 8301",+    "about": "Occaecat Lorem nostrud sint esse ullamco voluptate cillum sit aliquip sint nostrud. Velit aliqua cupidatat eu adipisicing sunt aute dolore cillum. Voluptate laboris exercitation magna non elit. Excepteur eiusmod enim amet fugiat quis. Anim cillum aute sit magna fugiat ut aliquip incididunt enim.\r\n",+    "registered": "2014-01-29T03:48:07 +08:00",+    "latitude": 70.685812,+    "longitude": -159.885272,+    "tags": [+      "adipisicing",+      "officia",+      "laborum",+      "ipsum",+      "voluptate",+      "sint",+      "cupidatat"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Vinson Berg"+      },+      {+        "id": 1,+        "name": "Emily Strong"+      },+      {+        "id": 2,+        "name": "Lori Gonzalez"+      }+    ],+    "greeting": "Hello, Sherri Villarreal! You have 1 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90ed7615708c5d1f8e",+    "index": 83,+    "guid": "cb7d9a4c-9739-43c8-b75e-0d3cdf462e89",+    "isActive": false,+    "balance": "$3,087.41",+    "picture": "http://placehold.it/32x32",+    "age": 28,+    "eyeColor": "brown",+    "name": "Little Nielsen",+    "gender": "male",+    "company": "LUDAK",+    "email": "littlenielsen@ludak.com",+    "phone": "+1 (826) 557-2684",+    "address": "842 Banner Avenue, Gadsden, Kentucky, 3196",+    "about": "Officia nostrud anim aliqua veniam ut. Non culpa et dolore nostrud elit esse sint mollit anim pariatur id velit irure culpa. Officia irure adipisicing velit velit culpa sunt proident occaecat irure voluptate ullamco. Labore mollit pariatur minim in cillum deserunt. Sint irure ex laborum et minim id ea fugiat excepteur velit veniam velit consequat sint. Culpa anim velit elit officia irure exercitation ex labore est amet. Do do eiusmod ipsum officia veniam laboris irure sint eiusmod non cillum eiusmod.\r\n",+    "registered": "2014-09-19T15:02:29 +07:00",+    "latitude": 26.743234,+    "longitude": -53.174043,+    "tags": [+      "et",+      "sint",+      "culpa",+      "dolore",+      "ea",+      "irure",+      "excepteur"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Gillespie Branch"+      },+      {+        "id": 1,+        "name": "Cantu Kent"+      },+      {+        "id": 2,+        "name": "Nell Meyer"+      }+    ],+    "greeting": "Hello, Little Nielsen! You have 5 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a909137471ca6b126c8",+    "index": 84,+    "guid": "169b0cbf-781c-4b46-8285-e18b3aa6bc0e",+    "isActive": true,+    "balance": "$2,853.96",+    "picture": "http://placehold.it/32x32",+    "age": 33,+    "eyeColor": "green",+    "name": "Kristi Marquez",+    "gender": "female",+    "company": "RONELON",+    "email": "kristimarquez@ronelon.com",+    "phone": "+1 (838) 414-3765",+    "address": "566 Love Lane, Fresno, New Jersey, 5219",+    "about": "Proident enim commodo ut nisi minim esse eiusmod nisi irure consequat proident voluptate. Culpa ut sunt incididunt sint minim duis excepteur nulla cupidatat ullamco proident nisi ex enim. Qui culpa ex velit do pariatur Lorem dolor ad labore adipisicing magna sunt voluptate dolore. Lorem minim commodo non amet. Nulla reprehenderit consectetur ad nisi voluptate. Ea consectetur deserunt dolore ut. Esse duis consequat reprehenderit cillum commodo consequat ex consectetur do sint velit non deserunt.\r\n",+    "registered": "2014-02-21T07:19:48 +08:00",+    "latitude": 38.750345,+    "longitude": -111.349096,+    "tags": [+      "minim",+      "minim",+      "occaecat",+      "tempor",+      "dolore",+      "fugiat",+      "laborum"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Eddie Soto"+      },+      {+        "id": 1,+        "name": "Valencia Serrano"+      },+      {+        "id": 2,+        "name": "Sears Richards"+      }+    ],+    "greeting": "Hello, Kristi Marquez! You have 3 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90deb8bb21d9c88b7a",+    "index": 85,+    "guid": "58c6180e-8bc6-4ae0-9988-52df23beedac",+    "isActive": false,+    "balance": "$2,028.59",+    "picture": "http://placehold.it/32x32",+    "age": 39,+    "eyeColor": "blue",+    "name": "Callie Sweeney",+    "gender": "female",+    "company": "POLARIA",+    "email": "calliesweeney@polaria.com",+    "phone": "+1 (999) 576-3446",+    "address": "278 Gunnison Court, Wakulla, Nevada, 3605",+    "about": "Minim non eu sint culpa. Non irure ea eiusmod est dolore id culpa laborum ex aliquip et eu. Laborum occaecat labore incididunt et ipsum adipisicing cupidatat eu consectetur reprehenderit quis dolor.\r\n",+    "registered": "2014-11-06T17:55:38 +08:00",+    "latitude": 22.578553,+    "longitude": 70.713617,+    "tags": [+      "veniam",+      "ad",+      "aliqua",+      "id",+      "quis",+      "esse",+      "est"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Cochran Raymond"+      },+      {+        "id": 1,+        "name": "Perry Pugh"+      },+      {+        "id": 2,+        "name": "Walsh Cummings"+      }+    ],+    "greeting": "Hello, Callie Sweeney! You have 8 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a9092b4caeee42e4083",+    "index": 86,+    "guid": "b37a99a1-89ea-4257-bd5c-f8070c6e5d72",+    "isActive": false,+    "balance": "$2,333.35",+    "picture": "http://placehold.it/32x32",+    "age": 27,+    "eyeColor": "brown",+    "name": "Henson Parrish",+    "gender": "male",+    "company": "MANTRO",+    "email": "hensonparrish@mantro.com",+    "phone": "+1 (983) 409-3702",+    "address": "749 Malbone Street, Century, California, 6571",+    "about": "Pariatur nisi magna laboris ipsum ad sint. Exercitation sit id reprehenderit fugiat ea dolor. Duis exercitation voluptate nulla duis enim culpa eiusmod nulla mollit et occaecat eu Lorem.\r\n",+    "registered": "2014-06-21T02:15:00 +07:00",+    "latitude": 42.27087,+    "longitude": 88.230275,+    "tags": [+      "sunt",+      "excepteur",+      "veniam",+      "cillum",+      "sunt",+      "consequat",+      "ullamco"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Dunn Cantrell"+      },+      {+        "id": 1,+        "name": "Mendoza Keller"+      },+      {+        "id": 2,+        "name": "Josefa Green"+      }+    ],+    "greeting": "Hello, Henson Parrish! You have 7 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a9001009b488b9deadf",+    "index": 87,+    "guid": "4a8e033a-6160-44a0-b6f6-e07f11455db9",+    "isActive": false,+    "balance": "$2,886.19",+    "picture": "http://placehold.it/32x32",+    "age": 33,+    "eyeColor": "blue",+    "name": "Mcgowan Hogan",+    "gender": "male",+    "company": "MAGNEMO",+    "email": "mcgowanhogan@magnemo.com",+    "phone": "+1 (816) 507-2353",+    "address": "979 Reed Street, Roberts, Maryland, 9773",+    "about": "Sunt sint labore aliqua nisi ipsum mollit. Lorem aliquip nisi Lorem eu velit. Laborum reprehenderit minim cillum culpa cupidatat commodo do aute dolore. Sunt ullamco consectetur ex officia anim fugiat quis fugiat laboris est quis officia aliqua pariatur. Adipisicing nulla culpa officia ut reprehenderit ea cupidatat.\r\n",+    "registered": "2014-05-26T12:57:18 +07:00",+    "latitude": -24.348796,+    "longitude": 25.886101,+    "tags": [+      "ex",+      "ullamco",+      "non",+      "ea",+      "enim",+      "quis",+      "ipsum"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Terry Burton"+      },+      {+        "id": 1,+        "name": "Cara Estes"+      },+      {+        "id": 2,+        "name": "Johanna Dillard"+      }+    ],+    "greeting": "Hello, Mcgowan Hogan! You have 1 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a9021cae47006152c91",+    "index": 88,+    "guid": "ebae8f41-1ba7-4f91-8d61-1a1a0d6b515f",+    "isActive": false,+    "balance": "$2,168.49",+    "picture": "http://placehold.it/32x32",+    "age": 25,+    "eyeColor": "brown",+    "name": "Riddle Alvarado",+    "gender": "male",+    "company": "INTERLOO",+    "email": "riddlealvarado@interloo.com",+    "phone": "+1 (892) 530-2012",+    "address": "488 Loring Avenue, Homeworth, Washington, 8337",+    "about": "Officia labore commodo eu consectetur. Consectetur consequat voluptate minim dolor do anim magna deserunt ullamco. Mollit laborum eiusmod veniam velit officia dolore dolore in ex veniam officia eu elit fugiat. Labore non in Lorem nulla irure ea id cupidatat eu consectetur. Tempor non quis eu nulla aliquip proident elit commodo duis laboris laborum eu amet occaecat.\r\n",+    "registered": "2014-11-13T11:12:08 +08:00",+    "latitude": 21.377099,+    "longitude": 128.211659,+    "tags": [+      "ex",+      "excepteur",+      "magna",+      "enim",+      "magna",+      "et",+      "incididunt"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Elisa Vinson"+      },+      {+        "id": 1,+        "name": "Peterson Reynolds"+      },+      {+        "id": 2,+        "name": "Mason Odonnell"+      }+    ],+    "greeting": "Hello, Riddle Alvarado! You have 1 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a901459251e4d519953",+    "index": 89,+    "guid": "6fe0652a-7055-47d0-8b6a-8bc3b3f6a7d0",+    "isActive": true,+    "balance": "$1,638.04",+    "picture": "http://placehold.it/32x32",+    "age": 29,+    "eyeColor": "blue",+    "name": "Stone Maynard",+    "gender": "male",+    "company": "CONJURICA",+    "email": "stonemaynard@conjurica.com",+    "phone": "+1 (827) 532-2021",+    "address": "631 Orient Avenue, Homestead, Utah, 8151",+    "about": "In voluptate voluptate veniam reprehenderit dolor est ut est voluptate dolore occaecat voluptate. Laboris cillum ex aliquip quis dolor qui qui fugiat sit. Veniam magna dolor mollit sint laborum duis id duis aliqua dolore reprehenderit qui et. Irure cupidatat ea sunt anim amet mollit adipisicing id sint deserunt veniam irure. Reprehenderit elit mollit do ex reprehenderit ipsum aliqua qui. Ea non eiusmod officia laboris dolor tempor deserunt elit velit ex aliqua. Duis reprehenderit duis duis elit irure elit dolore dolore.\r\n",+    "registered": "2014-05-26T12:55:16 +07:00",+    "latitude": -20.476755,+    "longitude": 76.876321,+    "tags": [+      "fugiat",+      "dolore",+      "consequat",+      "do",+      "esse",+      "consectetur",+      "anim"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Mckenzie Bartlett"+      },+      {+        "id": 1,+        "name": "Villarreal Welch"+      },+      {+        "id": 2,+        "name": "Amparo Kelly"+      }+    ],+    "greeting": "Hello, Stone Maynard! You have 5 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a9015625be6710ded3e",+    "index": 90,+    "guid": "d079e795-b71e-446b-91f8-6347ca0e2e57",+    "isActive": false,+    "balance": "$1,156.54",+    "picture": "http://placehold.it/32x32",+    "age": 28,+    "eyeColor": "blue",+    "name": "Baird Jones",+    "gender": "male",+    "company": "KNEEDLES",+    "email": "bairdjones@kneedles.com",+    "phone": "+1 (898) 574-3748",+    "address": "890 Lancaster Avenue, Falconaire, Wisconsin, 9406",+    "about": "Officia veniam sunt minim ex tempor labore consequat laborum adipisicing officia. Incididunt enim excepteur anim et do cupidatat magna excepteur culpa. Dolore enim ea excepteur non commodo aliqua nostrud commodo exercitation dolor duis. Consectetur consequat ullamco in esse fugiat pariatur anim mollit aliqua aliquip qui. Voluptate cupidatat ipsum labore aliquip fugiat cillum amet sunt voluptate occaecat occaecat nostrud.\r\n",+    "registered": "2014-01-22T19:38:09 +08:00",+    "latitude": -30.096555,+    "longitude": -119.933566,+    "tags": [+      "occaecat",+      "qui",+      "enim",+      "in",+      "pariatur",+      "irure",+      "officia"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Gail Flynn"+      },+      {+        "id": 1,+        "name": "Sanford Harmon"+      },+      {+        "id": 2,+        "name": "Noble Chambers"+      }+    ],+    "greeting": "Hello, Baird Jones! You have 3 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a90b147f5343b9ce16b",+    "index": 91,+    "guid": "af036dfd-72dd-451e-b93d-6dceea4796c4",+    "isActive": false,+    "balance": "$3,703.12",+    "picture": "http://placehold.it/32x32",+    "age": 33,+    "eyeColor": "blue",+    "name": "Leila Gay",+    "gender": "female",+    "company": "ZOXY",+    "email": "leilagay@zoxy.com",+    "phone": "+1 (845) 467-2535",+    "address": "800 Little Street, Troy, Louisiana, 233",+    "about": "Ea aliqua excepteur duis est velit eiusmod irure est eiusmod non elit. Consequat nostrud elit ullamco minim labore deserunt commodo. Mollit amet duis laboris mollit est ex aliquip. Minim et veniam reprehenderit voluptate aliqua id officia excepteur consequat fugiat non ex eu. Fugiat officia cupidatat pariatur voluptate excepteur in.\r\n",+    "registered": "2014-08-02T18:50:58 +07:00",+    "latitude": 59.481938,+    "longitude": 141.038749,+    "tags": [+      "ipsum",+      "commodo",+      "velit",+      "sit",+      "aliquip",+      "ullamco",+      "nisi"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Conway Summers"+      },+      {+        "id": 1,+        "name": "Deirdre Moody"+      },+      {+        "id": 2,+        "name": "Curtis Dickerson"+      }+    ],+    "greeting": "Hello, Leila Gay! You have 10 unread messages.",+    "favoriteFruit": "apple"+  },+  {+    "_id": "54727a90ffed9e529a6fa6b2",+    "index": 92,+    "guid": "7deb0cd2-5374-40c4-a38d-060ef674b2df",+    "isActive": true,+    "balance": "$2,980.94",+    "picture": "http://placehold.it/32x32",+    "age": 20,+    "eyeColor": "brown",+    "name": "Alma England",+    "gender": "female",+    "company": "SOLAREN",+    "email": "almaengland@solaren.com",+    "phone": "+1 (941) 595-3246",+    "address": "727 Covert Street, Nutrioso, Florida, 9152",+    "about": "Commodo quis occaecat voluptate tempor commodo non ipsum quis tempor pariatur elit. Ea fugiat velit dolor ea et occaecat eu ad aute ipsum. In sunt ad reprehenderit duis. Qui deserunt voluptate proident eu officia laborum pariatur incididunt id nisi. Eiusmod adipisicing do pariatur et incididunt aliquip consectetur.\r\n",+    "registered": "2014-08-08T20:51:29 +07:00",+    "latitude": 70.990007,+    "longitude": -119.193428,+    "tags": [+      "irure",+      "cillum",+      "incididunt",+      "nisi",+      "magna",+      "ullamco",+      "ex"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Downs Keith"+      },+      {+        "id": 1,+        "name": "Dawn Velazquez"+      },+      {+        "id": 2,+        "name": "Deanna Simpson"+      }+    ],+    "greeting": "Hello, Alma England! You have 9 unread messages.",+    "favoriteFruit": "strawberry"+  },+  {+    "_id": "54727a9075e4734b622a166a",+    "index": 93,+    "guid": "c0adc5f1-039a-4b52-a8cf-0196f3c23129",+    "isActive": true,+    "balance": "$2,658.69",+    "picture": "http://placehold.it/32x32",+    "age": 21,+    "eyeColor": "brown",+    "name": "Lou Rose",+    "gender": "female",+    "company": "INFOTRIPS",+    "email": "lourose@infotrips.com",+    "phone": "+1 (845) 564-3943",+    "address": "343 Krier Place, Evergreen, Indiana, 5249",+    "about": "Tempor ullamco id reprehenderit sunt veniam incididunt laboris. Ea ut officia do velit dolor officia Lorem consectetur nisi velit nisi. Aliquip dolore minim sint deserunt deserunt est.\r\n",+    "registered": "2014-04-22T07:46:26 +07:00",+    "latitude": 12.216368,+    "longitude": 8.875707,+    "tags": [+      "velit",+      "consectetur",+      "Lorem",+      "est",+      "adipisicing",+      "ad",+      "ullamco"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Coleen Foley"+      },+      {+        "id": 1,+        "name": "Boone Fitzpatrick"+      },+      {+        "id": 2,+        "name": "Elise Osborne"+      }+    ],+    "greeting": "Hello, Lou Rose! You have 5 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a90512b4f65287789e9",+    "index": 94,+    "guid": "26d470db-29c3-4c14-894b-5654462385a5",+    "isActive": false,+    "balance": "$2,751.47",+    "picture": "http://placehold.it/32x32",+    "age": 23,+    "eyeColor": "blue",+    "name": "Velez Wong",+    "gender": "male",+    "company": "FRANSCENE",+    "email": "velezwong@franscene.com",+    "phone": "+1 (821) 577-3492",+    "address": "197 Huron Street, Hilltop, New Hampshire, 6969",+    "about": "Minim id ullamco ullamco do proident Lorem commodo tempor ea. Est ut id non proident eiusmod. Deserunt fugiat enim culpa veniam sint.\r\n",+    "registered": "2014-07-21T05:23:01 +07:00",+    "latitude": -37.262537,+    "longitude": 132.661281,+    "tags": [+      "culpa",+      "non",+      "elit",+      "nulla",+      "laboris",+      "elit",+      "ex"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Charmaine Curry"+      },+      {+        "id": 1,+        "name": "Clara Randall"+      },+      {+        "id": 2,+        "name": "Lauren Jacobs"+      }+    ],+    "greeting": "Hello, Velez Wong! You have 4 unread messages.",+    "favoriteFruit": "banana"+  },+  {+    "_id": "54727a9095dc4743a5a84bd7",+    "index": 95,+    "guid": "1d1b9166-affc-4d15-ab60-b2edc51f29b4",+    "isActive": true,+    "balance": "$1,063.39",+    "picture": "http://placehold.it/32x32",+    "age": 22,+    "eyeColor": "brown",+    "name": "Catalina Baxter",+    "gender": "female",+    "company": "GAZAK",+    "email": "catalinabaxter@gazak.com",+    "phone": "+1 (839) 435-3437",+    "address": "904 Miller Avenue, Callaghan, Kansas, 7896",+    "about": "Ut ex exercitation commodo sint sint ipsum ea incididunt. Sunt nostrud tempor sint occaecat qui laborum culpa ipsum ullamco veniam est excepteur nostrud. Consequat occaecat id adipisicing esse culpa occaecat. Proident veniam laboris aute minim dolore eiusmod nulla minim elit sint.\r\n",+    "registered": "2014-05-02T10:13:41 +07:00",+    "latitude": 18.545597,+    "longitude": -99.991313,+    "tags": [+      "do",+      "proident",+      "reprehenderit",+      "minim",+      "cillum",+      "quis",+      "esse"+    ],+    "friends": [+      {+        "id": 0,+        "name": "Monique Wilcox"+      },+      {+        "id": 1,+        "name": "Hilda Craig"+      },+      {+        "id": 2,+        "name": "Leola Higgins"+      }+    ],+    "greeting": "Hello, Catalina Baxter! You have 10 unread messages.",+    "favoriteFruit": "apple"+  }+]
+ test/json-data/dates-fract.json view
@@ -0,0 +1,1 @@+["2015-02-02T09:10:11.123Z","2015-02-03T09:10:11.000+0000","2014-01-02T09:10:11.333Z","2014-01-03T02:09:12.000-02:00","2013-05-06T07:08:09.444Z","2015-02-02T09:10:11.66+03:00","2015-02-03T09:10:11.66Z","2014-01-02T09:10:11.66-1200","2014-01-03T02:09:12.66Z","2013-05-06T07:08:09.66+00:00"]
+ test/json-data/dates.json view
@@ -0,0 +1,1 @@+["2015-02-02T09:10:11Z","2015-02-03T09:10:11+0000","2014-01-02T09:10:11Z","2014-01-03T02:09:12-0300","2013-05-06T07:08:09Z","2015-02-02T09:10:11+04:00","2015-02-03T09:10:11Z","2014-01-02T09:10:11-11:45","2014-01-03T02:09:12Z","2013-05-06T07:08:09+0000"]
+ test/json-data/example.json view
@@ -0,0 +1,88 @@+{"web-app": {+  "servlet": [   +    {+      "servlet-name": "cofaxCDS",+      "servlet-class": "org.cofax.cds.CDSServlet",+      "init-param": {+        "configGlossary:installationAt": "Philadelphia, PA",+        "configGlossary:adminEmail": "ksm@pobox.com",+        "configGlossary:poweredBy": "Cofax",+        "configGlossary:poweredByIcon": "/images/cofax.gif",+        "configGlossary:staticPath": "/content/static",+        "templateProcessorClass": "org.cofax.WysiwygTemplate",+        "templateLoaderClass": "org.cofax.FilesTemplateLoader",+        "templatePath": "templates",+        "templateOverridePath": "",+        "defaultListTemplate": "listTemplate.htm",+        "defaultFileTemplate": "articleTemplate.htm",+        "useJSP": false,+        "jspListTemplate": "listTemplate.jsp",+        "jspFileTemplate": "articleTemplate.jsp",+        "cachePackageTagsTrack": 200,+        "cachePackageTagsStore": 200,+        "cachePackageTagsRefresh": 60,+        "cacheTemplatesTrack": 100,+        "cacheTemplatesStore": 50,+        "cacheTemplatesRefresh": 15,+        "cachePagesTrack": 200,+        "cachePagesStore": 100,+        "cachePagesRefresh": 10,+        "cachePagesDirtyRead": 10,+        "searchEngineListTemplate": "forSearchEnginesList.htm",+        "searchEngineFileTemplate": "forSearchEngines.htm",+        "searchEngineRobotsDb": "WEB-INF/robots.db",+        "useDataStore": true,+        "dataStoreClass": "org.cofax.SqlDataStore",+        "redirectionClass": "org.cofax.SqlRedirection",+        "dataStoreName": "cofax",+        "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver",+        "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon",+        "dataStoreUser": "sa",+        "dataStorePassword": "dataStoreTestQuery",+        "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';",+        "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log",+        "dataStoreInitConns": 10,+        "dataStoreMaxConns": 100,+        "dataStoreConnUsageLimit": 100,+        "dataStoreLogLevel": "debug",+        "maxUrlLength": 500}},+    {+      "servlet-name": "cofaxEmail",+      "servlet-class": "org.cofax.cds.EmailServlet",+      "init-param": {+      "mailHost": "mail1",+      "mailHostOverride": "mail2"}},+    {+      "servlet-name": "cofaxAdmin",+      "servlet-class": "org.cofax.cds.AdminServlet"},+ +    {+      "servlet-name": "fileServlet",+      "servlet-class": "org.cofax.cds.FileServlet"},+    {+      "servlet-name": "cofaxTools",+      "servlet-class": "org.cofax.cms.CofaxToolsServlet",+      "init-param": {+        "templatePath": "toolstemplates/",+        "log": 1,+        "logLocation": "/usr/local/tomcat/logs/CofaxTools.log",+        "logMaxSize": "",+        "dataLog": 1,+        "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log",+        "dataLogMaxSize": "",+        "removePageCache": "/content/admin/remove?cache=pages&id=",+        "removeTemplateCache": "/content/admin/remove?cache=templates&id=",+        "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder",+        "lookInContext": 1,+        "adminGroupID": 4,+        "betaServer": true}}],+  "servlet-mapping": {+    "cofaxCDS": "/",+    "cofaxEmail": "/cofaxutil/aemail/*",+    "cofaxAdmin": "/admin/*",+    "fileServlet": "/static/*",+    "cofaxTools": "/tools/*"},+ +  "taglib": {+    "taglib-uri": "cofax.tld",+    "taglib-location": "/WEB-INF/tlds/cofax.tld"}}}
+ test/json-data/geometry.json view
@@ -0,0 +1,1 @@+{"geometry": {"type": "Polygon", "coordinates": [[[-0.939359853061118, 51.57401065198471], [-0.939440892477492, 51.57431080772057], [-0.939543636564569, 51.57460846194951], [-0.939564627628489, 51.574822653966265], [-0.939763179055758, 51.575406210178826], [-0.939787182642249, 51.575552991614], [-0.939819571231525, 51.57564949527348], [-0.939851290904264, 51.57571272360808], [-0.939975682132949, 51.57588648747347], [-0.940036822193859, 51.57598774653439], [-0.940072640734852, 51.57606090267849], [-0.940109693338918, 51.576205104339515], [-0.940125502187331, 51.57645611534466], [-0.940118623499009, 51.57662779459441], [-0.94008851367835, 51.57680555827417], [-0.939998015208524, 51.57709787034017], [-0.939928028667517, 51.577252794653916], [-0.939830628174189, 51.57740747134858], [-0.939796941313687, 51.57749078985784], [-0.939785586773343, 51.57754463746666], [-0.939770656147318, 51.57769016804059], [-0.939658154074368, 51.57824573765943], [-0.939485738834128, 51.57883313597966], [-0.93945717030888, 51.579192545519916], [-0.939524336079755, 51.57928306919007], [-0.939540939284811, 51.57931379092206], [-0.939553921057406, 51.579748206813974], [-0.939549110634237, 51.57983088690364], [-0.939520940649101, 51.58004913048237], [-0.93951863934971, 51.58014801827346], [-0.939525037302583, 51.580245186310094], [-0.939540364690294, 51.58033074581249], [-0.939561798495469, 51.58040197377017], [-0.939594984852117, 51.58046431619436], [-0.939708262827559, 51.580619996404984], [-0.939729069878358, 51.58065615108302], [-0.939759224390201, 51.58072476025555], [-0.939813615319267, 51.58093026195459], [-0.939900523550716, 51.58172321441459], [-0.939894144544868, 51.581997403240464], [-0.939910832471369, 51.58208657162566], [-0.940114353648015, 51.582457967531546], [-0.94022333049887, 51.58267475179605], [-0.940267247821581, 51.58277225843995], [-0.94038427828014, 51.583077233742756], [-0.940493362377966, 51.583289522851196], [-0.940571515796737, 51.58340442254201], [-0.940725857371931, 51.58359464061899], [-0.941198340240742, 51.584069167875626], [-0.941335307610427, 51.584261925920295], [-0.941372786463165, 51.5843261048293], [-0.941416540699353, 51.58443080287741], [-0.941461326187118, 51.584739621364996], [-0.941517445876004, 51.5849954910945], [-0.941575869597484, 51.58533860085252], [-0.941574762730556, 51.58538624677686], [-0.941565185406358, 51.58542572382202], [-0.941550002863301, 51.58545795698674], [-0.941515976834126, 51.58549361692739], [-0.941470510606583, 51.585524677868065], [-0.941423872780384, 51.585544039043356], [-0.94131509921043, 51.585566436558274], [-0.941075026707647, 51.58558675057684], [-0.940953181611661, 51.58561262652464], [-0.940734943851404, 51.58568708689612], [-0.940229533088359, 51.585883040066975], [-0.940067635923264, 51.58595531027499], [-0.939917220191166, 51.58603038143916], [-0.939797255285709, 51.586099433232604], [-0.939675637701456, 51.586177461657186], [-0.939611074554604, 51.58622273608784], [-0.939340396866166, 51.586441486256], [-0.93912244296424, 51.58662744303347], [-0.938817233975319, 51.58671729893312], [-0.938661522913105, 51.586771639765516], [-0.938087678078033, 51.586992141762096], [-0.937332636663603, 51.5873072119485], [-0.93492634290483, 51.58836081134952], [-0.934774110277819, 51.58845114531925], [-0.934663126573178, 51.588505886685766], [-0.934565467487243, 51.588546362183585], [-0.934462140700832, 51.588582290320545], [-0.934337145222589, 51.58861892084846], [-0.934213782000904, 51.588647473549656], [-0.934055887760941, 51.588671216763295], [-0.933259701136762, 51.58864510257983], [-0.933213278816969, 51.588716614209304], [-0.932681740160827, 51.588792708227174], [-0.931992471253759, 51.58900853446031], [-0.930629113767652, 51.58946908647644], [-0.928525036745892, 51.59005953842682], [-0.928533690860491, 51.59024394687689], [-0.928456010745829, 51.59047792078815], [-0.928256501298223, 51.5906748158337], [-0.927916132025856, 51.59066181734799], [-0.927671608556404, 51.590624516635486], [-0.927319567022116, 51.59061680481465], [-0.926850285435352, 51.59062240702726], [-0.926313746570405, 51.59066425819173], [-0.925516790702947, 51.59079184232522], [-0.925387133168341, 51.59078076476673], [-0.925293323156809, 51.59077990606886], [-0.925202187496562, 51.59078806346173], [-0.925112304116411, 51.59080432476366], [-0.925036768134577, 51.59082431403807], [-0.924968299737815, 51.59085066215826], [-0.924899640304234, 51.59088510100373], [-0.924851228495836, 51.590917926896914], [-0.924734051019008, 51.59105082982951], [-0.924694255912291, 51.59108553291224], [-0.924446018071168, 51.59126668934789], [-0.924204972773907, 51.59150995377465], [-0.923492520665307, 51.59215352189589], [-0.922775141969353, 51.59288246146081], [-0.922298613651092, 51.59343737077436], [-0.922101568270595, 51.59365046303728], [-0.92149319027045, 51.59422483935332], [-0.921029930136652, 51.59464499018023], [-0.919455791303858, 51.595519790418045], [-0.919908472816787, 51.59560757835559], [-0.919902358174605, 51.595621908790214], [-0.919721503870405, 51.59588190272939], [-0.919513222918683, 51.59614164398608], [-0.919057326435374, 51.59661490568889], [-0.918991959815932, 51.59669253146064], [-0.918853979663433, 51.59690975873926], [-0.91882720841051, 51.59694278142396], [-0.918711889332325, 51.597056812913756], [-0.918614808732714, 51.59713234806153], [-0.918297245740306, 51.59749448485627], [-0.918073703114389, 51.59778825130676], [-0.917853804369843, 51.59811082427763], [-0.917772408698363, 51.59831598343439], [-0.917709563771812, 51.59853030520902], [-0.917692745480418, 51.59856971355161], [-0.917634249201942, 51.59866178857289], [-0.917443039919903, 51.59887043094418], [-0.917335250471836, 51.598971042984616], [-0.916888284039272, 51.599309503426845], [-0.916921110547038, 51.59938623548714], [-0.916942485633783, 51.5995195094582], [-0.916937652924948, 51.599601289122916], [-0.916945811778801, 51.599683188627004], [-0.916964246289877, 51.599757989575366], [-0.917049297107238, 51.59994580090974], [-0.917089705367533, 51.60000731699611], [-0.917171120773417, 51.60010517795964], [-0.91752823274465, 51.60044925531009], [-0.917834023417531, 51.600765883511485], [-0.917913079603466, 51.600902386385464], [-0.918033251654948, 51.60107073894696], [-0.91851090277833, 51.601507638962644], [-0.918694916056997, 51.601663091331545], [-0.919167224655989, 51.60214309940451], [-0.919302231291783, 51.6024167896932], [-0.919552009673002, 51.60266096432073], [-0.919812571846437, 51.60293760761218], [-0.920091738726093, 51.60358667644501], [-0.920208566517809, 51.603592246724574], [-0.920644522919282, 51.60359715428394], [-0.921093152621479, 51.6036156641763], [-0.921168285587618, 51.603613657034074], [-0.921469264587163, 51.6035867495928], [-0.921584776688781, 51.603586911416876], [-0.921718864440287, 51.603595336217005], [-0.921999521996191, 51.60363387995426], [-0.922136050325758, 51.603661209181794], [-0.922248015684399, 51.60368921195661], [-0.922346882036963, 51.60372159023333], [-0.922478548042792, 51.60377135359644], [-0.922605777045377, 51.603825571929555], [-0.92269948345521, 51.60389297005248], [-0.922855181916126, 51.60396453348124], [-0.922908065211369, 51.60398749776935], [-0.923525984356841, 51.604174796116844], [-0.923689774691443, 51.6042095667925], [-0.923842334751645, 51.60423074681459], [-0.924020987014581, 51.604247669912816], [-0.924152253615336, 51.604253368572344], [-0.924379056519158, 51.60424915231234], [-0.924491916632975, 51.60423939614583], [-0.925129396784027, 51.604149022807746], [-0.925308707040747, 51.60413807581467], [-0.925396812395019, 51.604137083863186], [-0.925816640142735, 51.60415261457396], [-0.926339243350925, 51.604157394450915], [-0.926627592983669, 51.60417621577326], [-0.928219342008724, 51.60433911850777], [-0.928540897883304, 51.60435823822504], [-0.928666454251138, 51.6043611822271], [-0.928925123731038, 51.60435275210506], [-0.929272427371303, 51.60432085222454], [-0.929350681081097, 51.604308977474346], [-0.929525019586135, 51.60426381040009], [-0.929730308556221, 51.60419195021111], [-0.929806393059037, 51.60414948374236], [-0.929854111423592, 51.60408517871234], [-0.929877477717563, 51.604012559228565], [-0.929880189120251, 51.60395863398061], [-0.930157001898001, 51.603362311928585], [-0.930233415197893, 51.60324431822904], [-0.930397156019029, 51.60303540508514], [-0.93058677482955, 51.602831223239896], [-0.930747612253953, 51.60268432562219], [-0.931065151604954, 51.602443542878724], [-0.931358556224333, 51.60224659876122], [-0.931494751600125, 51.602165114969594], [-0.931807666849096, 51.601998019766675], [-0.932115553298882, 51.60186055047904], [-0.932344261944379, 51.601774512081256], [-0.932581062209752, 51.601712824287226], [-0.932837650566022, 51.60166929919289], [-0.93304760157037, 51.60164423223924], [-0.933247236692325, 51.60162806284857], [-0.933524572231589, 51.601623388892406], [-0.93367307135076, 51.60163283011416], [-0.933854562367985, 51.601651562386515], [-0.934222940436163, 51.60170526005669], [-0.934582321335698, 51.60177326158339], [-0.935176916778695, 51.60190903449902], [-0.935323237909383, 51.60194992468383], [-0.935449328012611, 51.60199153042619], [-0.935555166022244, 51.60203475076641], [-0.935754957696255, 51.60213547009268], [-0.935920944220561, 51.60219991608389], [-0.936105992086264, 51.60225194623237], [-0.936251123590137, 51.60228203448254], [-0.9362553703842, 51.602285669620606], [-0.935954861941404, 51.603034649313756], [-0.936025513047831, 51.60328615677915], [-0.937604653681525, 51.60374194595848], [-0.937842729718605, 51.6037494948452], [-0.937998245297976, 51.60376798569096], [-0.938149157504488, 51.60379812385097], [-0.938282536474463, 51.60383709493093], [-0.938398403200176, 51.60388400001471], [-0.938549544157173, 51.60396629131982], [-0.938542113846477, 51.60403725821696], [-0.938540670189083, 51.60403724516617], [-0.938690470772444, 51.60417707075241], [-0.93885671831091, 51.60429276731543], [-0.938949655717409, 51.60433227129961], [-0.939171393952754, 51.60435945147724], [-0.939491406642938, 51.60438302306183], [-0.939580914110842, 51.60438383145672], [-0.939698081584302, 51.604374998744255], [-0.939860253923854, 51.60435578228165], [-0.940131370864495, 51.60430877574863], [-0.940224037565311, 51.60429792302206], [-0.941065905352937, 51.604358569226044], [-0.941456261791824, 51.60439985396626], [-0.94172260946072, 51.604433725629356], [-0.942065117075164, 51.604483568721975], [-0.942405972956444, 51.60454238758566], [-0.942542307804962, 51.60457868292929], [-0.942668453620936, 51.60461848302318], [-0.942754322773386, 51.60465162617505], [-0.942930037674105, 51.6047332338937], [-0.94300827141212, 51.60478429141865], [-0.943050642772121, 51.60482513526179], [-0.943095505071305, 51.604883085645305], [-0.943175837511794, 51.60503037259752], [-0.943199044776033, 51.60508812802956], [-0.943264927978451, 51.605360268787045], [-0.94328295289835, 51.6055168856955], [-0.943285556153251, 51.605653582182974], [-0.94327790338372, 51.60573443814757], [-0.943262026480571, 51.60579633763445], [-0.943228365851772, 51.605877858692274], [-0.943092212068021, 51.60614458434088], [-0.943074097962666, 51.60624063192375], [-0.943061365824471, 51.606415854446915], [-0.943044862244347, 51.60650472320056], [-0.943017897594943, 51.606608783583646], [-0.942979591589439, 51.606703750216774], [-0.942905884027612, 51.60683076806164], [-0.942741267849139, 51.60707925374213], [-0.942655006715899, 51.607249318295594], [-0.942630361430127, 51.60731563455148], [-0.942616263944651, 51.607425205688976], [-0.942621958529523, 51.60749089598715], [-0.942636336634899, 51.60755576531453], [-0.942660904708633, 51.607617129739225], [-0.942696981288364, 51.607680396114986], [-0.942747433102709, 51.60774648940761], [-0.942856244975984, 51.6078490746924], [-0.94299552290573, 51.60794563997599], [-0.943040847342192, 51.60798381288104], [-0.943186973159248, 51.608158666884215], [-0.943285889122319, 51.60825217104693], [-0.943509689787491, 51.60844031193307], [-0.94379690141149, 51.609007570967925], [-0.943824004868445, 51.60908424372538], [-0.943831400135399, 51.609201201602104], [-0.943839541642426, 51.60922375393887], [-0.943852077204152, 51.6092436483043], [-0.943874740412961, 51.60926273459518], [-0.943900416430683, 51.60927645299323], [-0.944008015202393, 51.60930709311221], [-0.944074894993692, 51.60934995527111], [-0.944126815315737, 51.60941516198104], [-0.944143328071964, 51.60945037786937], [-0.944150844233183, 51.609499899486615], [-0.944148608042361, 51.60953404761941], [-0.944074023202655, 51.60969882315075], [-0.94403415265091, 51.60986121332522], [-0.944017357658548, 51.60996266784037], [-0.943992023164905, 51.61030771903426], [-0.943965568458423, 51.61051428886229], [-0.943938629028152, 51.61067949277199], [-0.943919488028492, 51.61075754789674], [-0.943885949921455, 51.61083367520212], [-0.943827406881077, 51.61092935920088], [-0.943739589608875, 51.61104186395993], [-0.943638735204063, 51.61115604969767], [-0.943533757272514, 51.611261206608894], [-0.942952360107171, 51.61179457356613], [-0.942768364001955, 51.61194397664764], [-0.942436714102017, 51.61217027698273], [-0.942010518369458, 51.61248743929258], [-0.941742162181165, 51.61266215641604], [-0.941369087685306, 51.61286829880399], [-0.941218061678922, 51.612965845186196], [-0.941055044244821, 51.613082165678385], [-0.940962318642569, 51.61315685920963], [-0.940802313735133, 51.613329853845215], [-0.940532273923746, 51.613638528438315], [-0.940201080800705, 51.61403027273604], [-0.940058816377521, 51.614185443187054], [-0.939985695447699, 51.614286388653916], [-0.939953592073967, 51.614362527773245], [-0.939906859457064, 51.614508669589505], [-0.939802341993177, 51.61490335803689], [-0.939554942446072, 51.615603370947234], [-0.939452722231603, 51.61596121406953], [-0.939421935085161, 51.61604275989804], [-0.939387192811998, 51.616108085051245], [-0.939207265482904, 51.61639149465681], [-0.939036585027605, 51.61671185322904], [-0.938852844916865, 51.61697274874371], [-0.938765881571911, 51.61710953487875], [-0.938636321443754, 51.617400592062225], [-0.93858227703968, 51.617550263869646], [-0.938563060896774, 51.61763101488344], [-0.938557821588965, 51.617793716095726], [-0.938568952285691, 51.6178738422429], [-0.938586194898738, 51.61793963701825], [-0.93864824402059, 51.61806608070808], [-0.938712639101221, 51.61815388154161], [-0.938801458484915, 51.61824729803767], [-0.938902019778203, 51.61833272809444], [-0.939001577491258, 51.61839926656718], [-0.939080060597956, 51.6184404379885], [-0.939144458958051, 51.618466196342986], [-0.93924483456823, 51.61849767462955], [-0.939389936057716, 51.61853135511866], [-0.939533886937889, 51.618552436752374], [-0.939605967169062, 51.618558482633226], [-0.939704125343146, 51.618561167280205], [-0.940011869234288, 51.61855765138392], [-0.940970156479417, 51.61846828865034], [-0.942814559287174, 51.61834913395653], [-0.9433943585448, 51.61832378069166], [-0.943791844341078, 51.61831207069643], [-0.944095316626396, 51.61830580819228], [-0.944345189840605, 51.61830625638639], [-0.944633949874169, 51.618311549349706], [-0.944866264500346, 51.618321729423634], [-0.945069571918896, 51.61833704343469], [-0.945259736594118, 51.618358533188456], [-0.945405071369022, 51.6183823175338], [-0.945538707551366, 51.61841229079249], [-0.945670733169093, 51.618449442740584], [-0.945789657802543, 51.61849097274293], [-0.945898390514756, 51.618536007789324], [-0.946002624502939, 51.61858819565799], [-0.946144975691139, 51.61867849034559], [-0.946290660655923, 51.61881197458077], [-0.946424293873759, 51.618966930407886], [-0.94648587943588, 51.619052004215185], [-0.946597289998464, 51.61923103785771], [-0.946748500992991, 51.619500344620654], [-0.946865282768134, 51.619759451627154], [-0.94692090631825, 51.619852564183816], [-0.946986743272162, 51.619941272483025], [-0.947121992710119, 51.62008904866086], [-0.947221067183078, 51.620177155655796], [-0.947357337089385, 51.62028088174252], [-0.947636341593443, 51.62045871900467], [-0.947732020809812, 51.62050633282523], [-0.947832261440868, 51.62054409665008], [-0.948024909405708, 51.6205835873772], [-0.948272727755043, 51.62061098363029], [-0.94903310890345, 51.62064836261257], [-0.949342820854191, 51.62068530212313], [-0.949548509996597, 51.62072310869445], [-0.949603327033706, 51.620726296510135], [-0.94966121922192, 51.62072141934501], [-0.949725095662238, 51.62070760405018], [-0.949813979767225, 51.620674230793945], [-0.949850856696741, 51.620766275113425], [-0.949938127431095, 51.62092800552347], [-0.95012170684362, 51.62142328688538], [-0.950137689181391, 51.621481875301626], [-0.950144591409982, 51.621558365780544], [-0.950143525838059, 51.62166715485939], [-0.950086848280287, 51.62218276713024], [-0.950081566444785, 51.62234906486587], [-0.950091380322934, 51.6227375905417], [-0.950012857804674, 51.623198158520914], [-0.949963053357781, 51.62366617649237], [-0.94993621019599, 51.62382778558582], [-0.949887611273865, 51.62405573809743], [-0.94981748429385, 51.62434014525264], [-0.949698012983878, 51.6246969431444], [-0.949601071539472, 51.62520410239216], [-0.949519655994856, 51.62547671925938], [-0.949509640700394, 51.62553507516918], [-0.949509569744091, 51.62560071332227], [-0.949517625184728, 51.6256898023632], [-0.949539397678852, 51.62581048474923], [-0.949602831958219, 51.626066413978656], [-0.949622600072634, 51.6262113557699], [-0.949625807500304, 51.62644786389373], [-0.949588359660548, 51.62688092470566], [-0.949587162842075, 51.62718303218221], [-0.949593421398238, 51.627287390862], [-0.949641886384285, 51.62762860642186], [-0.949677564559904, 51.627772791296934], [-0.949732061930258, 51.62791534613564], [-0.949847071945823, 51.62812767746571], [-0.95005020451512, 51.62890367131774], [-0.949860358893705, 51.62924185674224], [-0.949849459019953, 51.62927592739189], [-0.949848649628705, 51.62931098742953], [-0.949857806222275, 51.62935243070728], [-0.949882540677514, 51.62940750068883], [-0.949782851646605, 51.62940750843079], [-0.949684104107472, 51.6296170289926], [-0.949619670293909, 51.62984214210067], [-0.949589348281571, 51.62990391300069], [-0.949478823262764, 51.63006027755304], [-0.949419643728549, 51.63018293320961], [-0.949378577882241, 51.63033452402259], [-0.949364251310331, 51.63051692549393], [-0.949339228920643, 51.63059942443184], [-0.949248633732607, 51.63076855530058], [-0.949181283974238, 51.630869557872636], [-0.949044504574139, 51.63103647701982], [-0.948893935504611, 51.63136242402941], [-0.948761069414249, 51.631297394415576], [-0.948669492362367, 51.63125880992652], [-0.948537801268757, 51.63120547966423], [-0.948447461288637, 51.63117589768262], [-0.948353982967507, 51.63115707745748], [-0.948302104865814, 51.63115121794117], [-0.948079606840815, 51.63115102360314], [-0.947964190602531, 51.63114369566099], [-0.94782149485015, 51.63112892992514], [-0.947725107052886, 51.63111098229264], [-0.947684910040859, 51.63109983219839], [-0.947483021604023, 51.63102069524269], [-0.947447137482345, 51.63101048287877], [-0.947306094892948, 51.630986739722026], [-0.947000710620611, 51.63094713644845], [-0.946674033898238, 51.6308911565172], [-0.946364247153943, 51.63079216747898], [-0.946230490849763, 51.63076579091048], [-0.946172752135344, 51.63076347450912], [-0.94612066649297, 51.630766603772976], [-0.945914796224295, 51.63079712600587], [-0.94583241748209, 51.63079818492752], [-0.945382953779145, 51.63067905708907], [-0.945085984219956, 51.63058827220451], [-0.944883205963743, 51.630547786680644], [-0.944304065189339, 51.630538086402446], [-0.94394898927073, 51.630523204990325], [-0.943611477254356, 51.63049858971805], [-0.943603681926519, 51.6307098224617], [-0.943277115569317, 51.63102249010073], [-0.943359247728321, 51.63109426279834], [-0.942862682241593, 51.631385618132356], [-0.942777562378065, 51.63144239822567], [-0.942532313252267, 51.63161282917167], [-0.942337892928893, 51.63177202827151], [-0.94226606523137, 51.631816339444626], [-0.942197377359527, 51.63184988892168], [-0.942115813951467, 51.631877927416205], [-0.941937005967162, 51.631924871353945], [-0.941512981586086, 51.63201636159427], [-0.94144898217334, 51.6320346671529], [-0.94125937051438, 51.632111184999914], [-0.940790185204025, 51.6323425346351], [-0.940501042462563, 51.632535943664614], [-0.940243238615803, 51.63268647504363], [-0.940131608045529, 51.632764593869275], [-0.939328651391563, 51.63361514453957], [-0.939011405652477, 51.634145481445906], [-0.93882930544315, 51.63451878632893], [-0.93850479164515, 51.63486472819315], [-0.93841585373771, 51.63483694950617], [-0.938308667759282, 51.63503379600045], [-0.938237520065274, 51.63511048065125], [-0.938174497877949, 51.635148574806536], [-0.938097155068085, 51.63518114448376], [-0.938014243480848, 51.63520467214815], [-0.937885103100742, 51.635227781700706], [-0.937899482630853, 51.63529265131639], [-0.937852791962712, 51.635312010611116], [-0.937756752174709, 51.63534081433219], [-0.93768849662133, 51.635355482713884], [-0.937445207164159, 51.63537845841074], [-0.937282775985471, 51.6354039635962], [-0.936848828519967, 51.635485456519916], [-0.935951432459366, 51.63561220386278], [-0.935719677036262, 51.635636179646525], [-0.935420148631615, 51.63565414571281], [-0.935249454884316, 51.63560044703531], [-0.935077590540852, 51.635535048401415], [-0.934717727795324, 51.63535285185533], [-0.934413155884105, 51.635216113905294], [-0.934240481559584, 51.635123731852076], [-0.934130824273522, 51.63505529964683], [-0.934018404467151, 51.63498144729724], [-0.93379675026591, 51.63482118293064], [-0.933679892303923, 51.6347517857655], [-0.933595523374065, 51.63471415412531], [-0.933350473747258, 51.63462740768713], [-0.933261876876166, 51.63458524160035], [-0.933052343088727, 51.63446285066096], [-0.932887751506176, 51.63439571653097], [-0.932638117941304, 51.63431971687678], [-0.932307197466639, 51.63419801967628], [-0.931343887912331, 51.63380981222079], [-0.931071830619705, 51.63376597498289], [-0.930956833347264, 51.63374065092008], [-0.930823331188951, 51.63370346918478], [-0.930701576222194, 51.633658301797674], [-0.930601553796263, 51.63361063468632], [-0.930506119022842, 51.63355221932683], [-0.930386739592999, 51.633467510133144], [-0.930286034494439, 51.633387466714524], [-0.930225334024314, 51.63332666999925], [-0.930188937852857, 51.633276884582266], [-0.930114567680391, 51.63312155131756], [-0.92999324620862, 51.63293521875163], [-0.929900400330813, 51.63282827174111], [-0.929602366751531, 51.6325378240622], [-0.929499431873266, 51.632429885607195], [-0.929341000864788, 51.63222433167361], [-0.929059923028374, 51.631889079042224], [-0.928963213558906, 51.63176231446386], [-0.928896162303318, 51.6316645934755], [-0.928844852932023, 51.631573310156845], [-0.928688841592085, 51.63126527289447], [-0.928672375509884, 51.6312282570475], [-0.928615004479859, 51.631026321392014], [-0.928599834825331, 51.63099561149414], [-0.928552267379979, 51.63092953870757], [-0.928477466267079, 51.630854225808044], [-0.928389580173335, 51.630782390113474], [-0.928294323595921, 51.6307167812279], [-0.928190294396403, 51.63065558801307], [-0.928078937075949, 51.630598823630024], [-0.927494137115659, 51.630339021667446], [-0.927177150281751, 51.63017967221046], [-0.927053070740772, 51.63011110151437], [-0.926922044883713, 51.63003077810591], [-0.926774152423019, 51.6299305188448], [-0.926569924361936, 51.62976860145066], [-0.926465515994788, 51.62966244509719], [-0.926222881099496, 51.62935271335746], [-0.925807083215573, 51.62885077488713], [-0.925685153671251, 51.62869140695483], [-0.925627779063265, 51.628612654944924], [-0.925107975398826, 51.62780494620014], [-0.925039238852947, 51.627717997425265], [-0.924853081975605, 51.627525670804864], [-0.924566287633664, 51.62725149768458], [-0.924018135317732, 51.62666651663998], [-0.9236755124427, 51.62631000590676], [-0.923506463123086, 51.62618886774415], [-0.92325024557972, 51.62602736696645], [-0.923179326155173, 51.62597186775246], [-0.923151247824846, 51.62593744208942], [-0.923025917353845, 51.62573937605691], [-0.922746731858051, 51.625388839233764], [-0.922647729925315, 51.625421200084226], [-0.922438316532782, 51.62517470633975], [-0.922266691774008, 51.62491866820555], [-0.922176577184834, 51.624819832363215], [-0.921722712186069, 51.62440384906751], [-0.921324388846087, 51.624082786314446], [-0.921078577970903, 51.623909687134365], [-0.920860652296657, 51.62377910436709], [-0.920574889783577, 51.623646099345635], [-0.920239048968319, 51.623492851580735], [-0.920014292502202, 51.62371107994427], [-0.919774493442185, 51.623954346092496], [-0.9196346398006, 51.62406455586703], [-0.919395541436808, 51.62427815528515], [-0.919291601043514, 51.624396787581134], [-0.919216417933559, 51.6245210794288], [-0.919153464283899, 51.62467785367513], [-0.919134125069682, 51.624762197061855], [-0.919115026043568, 51.62495803894169], [-0.919049238753011, 51.62517323267518], [-0.919009465019033, 51.625266379578875], [-0.918926652164549, 51.625407685106715], [-0.91889138185638, 51.62549368011671], [-0.918887531877765, 51.62553410703284], [-0.918896749679156, 51.62569334383204], [-0.918892707420934, 51.62574186144837], [-0.918872585162246, 51.62579832350382], [-0.918836276012485, 51.62586722482348], [-0.918763089019947, 51.62596815648993], [-0.918486277330256, 51.626309088016214], [-0.918317614737411, 51.62653682130949], [-0.917905561785174, 51.626975413689514], [-0.917718470441796, 51.62724883363936], [-0.917588198962995, 51.627380709193844], [-0.917518986462533, 51.627435819379606], [-0.917391270738013, 51.627520961779815], [-0.916852199941663, 51.62783249660931], [-0.916708494011591, 51.62822500463541], [-0.916564194300437, 51.62882431452802], [-0.91642514281193, 51.62950639562586], [-0.916399138398425, 51.62974892972319], [-0.916394191477189, 51.62995659074321], [-0.917260413231125, 51.62992321851857], [-0.917422640699178, 51.63166639247214], [-0.91745600722272, 51.63232848375385], [-0.917204699151121, 51.632324368705305], [-0.916787813832452, 51.632356490901394], [-0.916473041038736, 51.63240843616508], [-0.91615824611563, 51.63246127954521], [-0.916096846672852, 51.632491284427324], [-0.916000979824314, 51.632573122584574], [-0.916035541448316, 51.63300234215989], [-0.916058746179642, 51.633180590521675], [-0.916070697537387, 51.63322475979131], [-0.916098260729099, 51.63328076225654], [-0.916287121367376, 51.6334803208717], [-0.916556537933578, 51.63369500893812], [-0.916833436931067, 51.63389897542989], [-0.916980624704717, 51.63396776999687], [-0.917236275570907, 51.634093312450645], [-0.91746761535009, 51.63414759643489], [-0.917599467742121, 51.63382870989293], [-0.917652284973834, 51.63367364156546], [-0.917677934007693, 51.63362802065708], [-0.917745133700401, 51.63359716923921], [-0.917749446066394, 51.633598108140106], [-0.917778102484095, 51.633608262991594], [-0.918084580428658, 51.633662338963], [-0.918258883815884, 51.633684625273325], [-0.918506861448772, 51.63370758977881], [-0.918953829589691, 51.63374767133924], [-0.919245145699076, 51.633771033193085], [-0.919300707852053, 51.63386505735126], [-0.919518444759651, 51.63449197843591], [-0.919277685382965, 51.63446908250848], [-0.919031520477806, 51.63467362436313], [-0.918786199566619, 51.63490335001199], [-0.918632752044247, 51.63503681158531], [-0.918623327237537, 51.63512933850577], [-0.918590569006466, 51.635413172104], [-0.918679820058316, 51.63554886817429], [-0.918329488250497, 51.63557621368664], [-0.918276435808259, 51.635741170885815], [-0.918247752304029, 51.6360358318421], [-0.918226764712395, 51.6364321689642], [-0.918316733144818, 51.63702374696847], [-0.918287332378286, 51.63783452012518], [-0.918306713699577, 51.63835621240955], [-0.918316674296085, 51.639213205140855], [-0.918347519668651, 51.639799742499974], [-0.918322169732004, 51.63995416480466], [-0.918318885834364, 51.64003146244247], [-0.91832686087795, 51.64012145202931], [-0.91837911416845, 51.640354816042915], [-0.918418495846573, 51.64070405330022], [-0.918530296534871, 51.64128953760163], [-0.918512952473936, 51.64147190762244], [-0.918367217757134, 51.64200916315153], [-0.917905817511154, 51.6420255938785], [-0.917874522823857, 51.64200462484983], [-0.9178264462409, 51.64196012300076], [-0.917792775459127, 51.641917552189355], [-0.917773553228143, 51.64187511449974], [-0.917765526057023, 51.64184806570082], [-0.917757894232724, 51.6417436926823], [-0.917749532884981, 51.64142711089125], [-0.917739557627689, 51.64136048104305], [-0.917720785029217, 51.641299165096676], [-0.917688816437575, 51.64124581999865], [-0.917649452591449, 51.641199600032465], [-0.917568381483579, 51.64114490323613], [-0.917433423830488, 51.64098630616572], [-0.917315173490429, 51.64079369481265], [-0.916601390868836, 51.640243120219885], [-0.916329281834837, 51.640018517003504], [-0.916001068304932, 51.63966211774633], [-0.915790319174844, 51.63947044935813], [-0.915502073944432, 51.63913509831614], [-0.915393357554307, 51.63902799332443], [-0.915169439572157, 51.63884339546647], [-0.914312592230519, 51.638234837864296], [-0.913969150495335, 51.63797270396478], [-0.913808719070197, 51.63773204326735], [-0.913709021859032, 51.63761063348384], [-0.913238840648949, 51.637814889395024], [-0.912211197236081, 51.63818662075986], [-0.912057780107923, 51.6382571327065], [-0.911911629192503, 51.63832591344631], [-0.911683066608314, 51.6384568717294], [-0.911571655864317, 51.638583520328496], [-0.910821072629623, 51.63897039510019], [-0.910799442348496, 51.63890815232427], [-0.910457034748542, 51.63884473254883], [-0.90959863753988, 51.63872527019996], [-0.909397970101408, 51.639078576087414], [-0.909048252097236, 51.63925965643008], [-0.909362542404515, 51.63959166868019], [-0.90780907162756, 51.64013291512084], [-0.906217399025103, 51.640939038154336], [-0.905898125060113, 51.64111499768922], [-0.905490384709512, 51.641363863281036], [-0.905329841704473, 51.64154939281248], [-0.905189541321435, 51.641794455508446], [-0.90489098319803, 51.64194992468491], [-0.904781623731163, 51.641990266433346], [-0.904676706895303, 51.64202615370086], [-0.904609919053975, 51.64203901826923], [-0.904237228330216, 51.64209218907456], [-0.90337364044862, 51.64224597828853], [-0.902975191138407, 51.642348358507896], [-0.90292303291413, 51.6422939216028], [-0.902563354675933, 51.64240655335107], [-0.902346768622897, 51.642458479170756], [-0.900824166346957, 51.64291090498388], [-0.900831401206159, 51.64297031739377], [-0.900456613238591, 51.64299018760407], [-0.900019098619797, 51.64297440185565], [-0.899740380937974, 51.64290615240652], [-0.899740489711103, 51.64290165761423], [-0.899316872364163, 51.642849133775655], [-0.899203336991784, 51.642822893203494], [-0.898949902655656, 51.64272520647125], [-0.898783095909619, 51.642631028913314], [-0.898613530793898, 51.64253143027416], [-0.898274969154989, 51.64230885935864], [-0.898003026265053, 51.642080618381755], [-0.897815191816853, 51.641840577798526], [-0.897681652652772, 51.6416262230417], [-0.897327401428262, 51.64091885338664], [-0.896908641882592, 51.6401308512027], [-0.896689777704333, 51.63985994571793], [-0.896199107519928, 51.639356299928586], [-0.896147069948886, 51.63929736526477], [-0.896071667668875, 51.63912941223961], [-0.896034388155249, 51.63881974973283], [-0.89604243255033, 51.63866696761571], [-0.896079873457203, 51.63849378104931], [-0.896158355572289, 51.63829670279626], [-0.896345617779906, 51.63796397393339], [-0.896436991088377, 51.637771512438086], [-0.896627579786388, 51.63748017587825], [-0.896853716275347, 51.63721255114492], [-0.896930541034958, 51.63714313797598], [-0.897052325259386, 51.63706695367484], [-0.897622203318074, 51.636806151704704], [-0.897875248140837, 51.636679946210485], [-0.898045509693898, 51.63657094674504], [-0.898161794997489, 51.63648302058021], [-0.898300490282884, 51.636364732965696], [-0.898761753797595, 51.63587451909389], [-0.898944791614438, 51.635595696246455], [-0.899167964890093, 51.63497197055003], [-0.899296205851425, 51.63450920414348], [-0.899574174378538, 51.633591065468096], [-0.89965934952667, 51.63323579470252], [-0.899745200652967, 51.632733067337156], [-0.89977291429982, 51.632065248259224], [-0.899749317297925, 51.63190587518938], [-0.899730833669311, 51.631833768913715], [-0.899698165870897, 51.631750739777836], [-0.89965967663145, 51.63166945441679], [-0.899415121605884, 51.63126703489972], [-0.899342021743563, 51.6311233827535], [-0.899274678557411, 51.630980683675816], [-0.899221406706553, 51.63067446858268], [-0.899099405183406, 51.63046202142283], [-0.899036514315775, 51.63037421293725], [-0.898969246754073, 51.63028816171176], [-0.898790586842518, 51.63008867047265], [-0.898592010806669, 51.62987640386868], [-0.898497321924236, 51.62978919602221], [-0.898330098469121, 51.62965455114822], [-0.898092642525963, 51.62949676796704], [-0.897090362270362, 51.62894426419184], [-0.896737033461809, 51.62879887742956], [-0.896347312922056, 51.6286648367393], [-0.895633444660858, 51.62837398981453], [-0.895432402381131, 51.628264199314984], [-0.895328663542796, 51.62819308870188], [-0.895257753282041, 51.62813847178444], [-0.895144755729953, 51.62803220648194], [-0.894948239644532, 51.627795675481295], [-0.894944118635818, 51.62766795529761], [-0.894659948504904, 51.62723368184], [-0.894597856997988, 51.62711350846984], [-0.894557343909314, 51.62699713484451], [-0.894469809950127, 51.62655661915932], [-0.894297931114171, 51.625960652861906], [-0.894027819576293, 51.62530531541029], [-0.893931965007508, 51.62514795788166], [-0.893770350871199, 51.62496210671055], [-0.893637995566781, 51.62476124514902], [-0.893507481839104, 51.62466290563429], [-0.892108104715385, 51.62369479825078], [-0.891788795844237, 51.623459800649094], [-0.891922368328922, 51.62337294285213], [-0.891402229815371, 51.62296161084893], [-0.891224922435199, 51.622768414566416], [-0.890089770742591, 51.621276764770215], [-0.890188416566163, 51.62120036880181], [-0.889947380146256, 51.62101466068101], [-0.889727152716072, 51.62086421627997], [-0.889328903798693, 51.62066892786315], [-0.889153387824523, 51.620758083474584], [-0.888409981594066, 51.6201459098237], [-0.888016456293258, 51.619876030785385], [-0.887588982852847, 51.619754191291285], [-0.887143566599165, 51.61965735685454], [-0.88701981132148, 51.61963820065132], [-0.886790744842007, 51.619613550199404], [-0.886603228328527, 51.619602780759124], [-0.886388075513318, 51.619599841439666], [-0.886090409498186, 51.61960421155123], [-0.8851368584814, 51.61967159165818], [-0.883828125903616, 51.61979133688442], [-0.883515973065361, 51.619797361107345], [-0.883424375160774, 51.6198216668555], [-0.883259776293847, 51.619877648328995], [-0.883163867754863, 51.61990101371408], [-0.883044634749604, 51.619815357981636], [-0.881854770311083, 51.61956485587701], [-0.881704286246311, 51.61951666620604], [-0.881635675440863, 51.619487239450166], [-0.881560131370525, 51.619446057447796], [-0.881477720403852, 51.619390423316226], [-0.881374708667671, 51.619291432867335], [-0.880834298239682, 51.61853547912722], [-0.880617549323506, 51.618127888748454], [-0.880388345702594, 51.61816796449318], [-0.879226788544387, 51.6185884841998], [-0.878534686661489, 51.61883184500677], [-0.878385021629543, 51.61898507235025], [-0.878148819949825, 51.61913297697072], [-0.878102165244257, 51.61915051459153], [-0.87804120240859, 51.61916252048532], [-0.877967464227806, 51.619165412606506], [-0.877896924603018, 51.61915574690363], [-0.877839648002824, 51.61913541789456], [-0.877812586845032, 51.61911987344738], [-0.877382402665225, 51.61869944717381], [-0.877261875140598, 51.61866682400238], [-0.877087293116294, 51.618600414623565], [-0.876592344555959, 51.61846350297477], [-0.876408990128558, 51.61845995037962], [-0.876374465270622, 51.61845422502283], [-0.876347205240439, 51.61844677083749], [-0.87630299358364, 51.61842386858449], [-0.876241963569583, 51.618380124514495], [-0.876216636094276, 51.618352907107045], [-0.876152781748704, 51.618131100785114], [-0.876106882196741, 51.61811807314475], [-0.876305643178539, 51.617614643124426], [-0.875030363348109, 51.6174927341714], [-0.874705473526163, 51.61748872204597], [-0.874564111923802, 51.6174810731233], [-0.874279482039409, 51.61730930128958], [-0.874259717266618, 51.61699889890464], [-0.874416434195306, 51.61673604585861], [-0.874423036602998, 51.61652750219062], [-0.870144618410033, 51.61769939147719], [-0.870074692270606, 51.617607003497994], [-0.869665459226217, 51.61768399010514], [-0.869137937974611, 51.617812887107476], [-0.868711126950696, 51.61789959196275], [-0.868460959800069, 51.617969115035805], [-0.866874516019656, 51.61827841686334], [-0.866642434191741, 51.6183175388698], [-0.866517885416195, 51.61833072340073], [-0.866392071649004, 51.61833670224208], [-0.865776872894858, 51.61833166083683], [-0.865633662902087, 51.618340168339564], [-0.865493072065054, 51.618359490991885], [-0.864874419821269, 51.618492883413026], [-0.864829070270363, 51.61851582340227], [-0.8648101986804, 51.61846169086974], [-0.864778464993556, 51.618402038980314], [-0.864733824417545, 51.61833866562683], [-0.864681918741689, 51.61827702035103], [-0.864649512487616, 51.61824433697217], [-0.864492159027339, 51.61812502442397], [-0.864510219622979, 51.618037979732996], [-0.864490008258273, 51.61792179156664], [-0.863791866117017, 51.616668790850674], [-0.863578496430752, 51.61619016667409], [-0.863939572338851, 51.6160749702936], [-0.864141702322916, 51.61602027805823], [-0.864343742093738, 51.615969181278444], [-0.864684853799959, 51.61590144547291], [-0.864287415633653, 51.61573755014447], [-0.864114344880943, 51.615611790807684], [-0.86394143216696, 51.615479738556914], [-0.863424226660599, 51.6150224515856], [-0.863108525016747, 51.61470918210108], [-0.862999136822108, 51.61463618943345], [-0.862947760773663, 51.614611414387845], [-0.862827479233939, 51.614569786988696], [-0.862764327092327, 51.6145538895073], [-0.862690954996651, 51.614542388851355], [-0.862422671693898, 51.614528100870835], [-0.862278208948609, 51.61452939875511], [-0.862025067586916, 51.614544929155834], [-0.861633333855888, 51.61461576315295], [-0.861195617216796, 51.61467715820802], [-0.860991546767721, 51.61469406144679], [-0.860819513274215, 51.61470048511226], [-0.860591345418716, 51.61469917048917], [-0.860366335624517, 51.61468709604235], [-0.860199196800327, 51.61467108707321], [-0.860140174693704, 51.6146633207896], [-0.859842784619867, 51.61460018922178], [-0.85969929013584, 51.61456282905675], [-0.859501466379857, 51.61450336097874], [-0.859306643699259, 51.614439425872085], [-0.859175017225196, 51.614389591981386], [-0.858977780895874, 51.61430675031806], [-0.858172868778381, 51.61392127488393], [-0.856305510909038, 51.61299763777437], [-0.855879431428414, 51.61282624136621], [-0.855413827571698, 51.612676038205095], [-0.855073660630441, 51.61259179657028], [-0.854915624510577, 51.612558784566126], [-0.854688372582268, 51.61252150056368], [-0.854459541396129, 51.6124895957323], [-0.854208848612115, 51.61246556965946], [-0.853413812340721, 51.612436228916096], [-0.851724243907319, 51.61242871018406], [-0.851450292115, 51.612410744947404], [-0.850998615679464, 51.61233888823236], [-0.850653274334699, 51.61223120468302], [-0.850372939737194, 51.61235164666859], [-0.850315593337778, 51.61233490012849], [-0.850193999216673, 51.612288751000705], [-0.850146601119627, 51.612278395984944], [-0.849926028861458, 51.61226274823948], [-0.849854084982468, 51.61225215268858], [-0.849714847013531, 51.612218418620564], [-0.849473435162227, 51.61217019573735], [-0.849372523204365, 51.61216291272777], [-0.849235958321253, 51.61219484371945], [-0.849170915863905, 51.6121969037703], [-0.849112126238191, 51.61218014249445], [-0.8487984760956, 51.61201881385575], [-0.848813983178796, 51.61186251023449], [-0.848805967905994, 51.611779707971536], [-0.848777693193183, 51.61169850541887], [-0.848751669639592, 51.61164250170296], [-0.848740414389996, 51.61163070213332], [-0.848685616135578, 51.61162746733029], [-0.848598109857592, 51.61148993537107], [-0.848561752293423, 51.61144282198033], [-0.848420551583887, 51.61131555337684], [-0.848346143135118, 51.61123120073643], [-0.848017978126262, 51.61073074025961], [-0.84803240586268, 51.61067423405594], [-0.848009203184881, 51.61062095534751], [-0.848010806265629, 51.61061467688053], [-0.848360723390725, 51.61054078070849], [-0.848731046077576, 51.61040234328455], [-0.848814480324059, 51.61035820278237], [-0.848820437461021, 51.610351067821576], [-0.848690278305866, 51.610072847084595], [-0.847947554822518, 51.60900543974003], [-0.847435103308081, 51.6083125448088], [-0.847167998407497, 51.60796823756615], [-0.846186886755928, 51.60662333116728], [-0.845549998419521, 51.605828497430636], [-0.845383680959569, 51.60566770865375], [-0.844840468159951, 51.60522267062485], [-0.84443530295085, 51.60497141150611], [-0.844232696249957, 51.60481835643492], [-0.843486943931864, 51.60416181103064], [-0.84332558359401, 51.60420428093958], [-0.842908147301968, 51.603868373383044], [-0.842663709069561, 51.603714004109015], [-0.842310199280505, 51.603533382087996], [-0.841471682375652, 51.60311958453644], [-0.841215384127901, 51.602977683579475], [-0.840873068217364, 51.60281155440105], [-0.840467421086961, 51.602637605395635], [-0.840268344470896, 51.602516948405764], [-0.840203154295604, 51.60246864832527], [-0.838474883614574, 51.601841924417336], [-0.838334815858368, 51.60178568911777], [-0.838125139250388, 51.60168470536249], [-0.837520083534699, 51.60134782062488], [-0.837365310402971, 51.60124558073874], [-0.837143016970701, 51.6010734356079], [-0.83702163004049, 51.600964332538595], [-0.836879440722268, 51.60082175386177], [-0.836733036616179, 51.60067463738169], [-0.836105420124444, 51.59992210370809], [-0.836001567218505, 51.59980508080317], [-0.835894758960057, 51.59969072601903], [-0.835646409012494, 51.5994652683849], [-0.835572705263831, 51.59941148620318], [-0.835355212679077, 51.599278049641256], [-0.835001490423373, 51.59910819321327], [-0.834666328550124, 51.59894661249016], [-0.834023851532118, 51.59860753956394], [-0.833813699592692, 51.59846967713012], [-0.832219972267628, 51.59727861587006], [-0.831975375091702, 51.59713321406168], [-0.83180286996819, 51.597047874561696], [-0.83156138306724, 51.59695015903964], [-0.831279067386554, 51.596868221715724], [-0.831119729288761, 51.59683246683902], [-0.830996129682144, 51.596810555210986], [-0.830798964246538, 51.59678611250204], [-0.829935838169309, 51.596720865686486], [-0.82967342169104, 51.596707460026586], [-0.829378950815281, 51.59670542343618], [-0.828672101908656, 51.596738836236966], [-0.828529092462618, 51.59674190522934], [-0.828241907711544, 51.596737240965595], [-0.828066024571149, 51.59672739285258], [-0.827854311754233, 51.596707295846045], [-0.827399248313258, 51.59666228882735], [-0.827164974703315, 51.59662128423301], [-0.827063074447412, 51.59659778687348], [-0.826830893268571, 51.59653162563389], [-0.826403126611598, 51.596379885757386], [-0.826320361645679, 51.596342192238744], [-0.826196596762853, 51.596270819297594], [-0.825930135887572, 51.596078429107884], [-0.825702044863314, 51.59590980062742], [-0.825239717878873, 51.595531118786504], [-0.824901250965003, 51.59522021379687], [-0.824412039116432, 51.59470908081712], [-0.8242790371805, 51.59460434384618], [-0.824162427084317, 51.59453573788591], [-0.823734544991122, 51.59433363318264], [-0.822923577534385, 51.59387232098135], [-0.82274220720895, 51.593740122024236], [-0.822758790294358, 51.59343366962803], [-0.822450529949737, 51.592123177421634], [-0.822408018987733, 51.591980681321466], [-0.82236394886053, 51.59184266543808], [-0.822242174035691, 51.59158338051977], [-0.821801765862476, 51.5908065697487], [-0.821372709449585, 51.59009371260957], [-0.821171850754746, 51.58982284235581], [-0.820679968304875, 51.58936291919855], [-0.820417155492676, 51.58914357740762], [-0.82015811620844, 51.5888901042506], [-0.820045903377484, 51.58876399099103], [-0.81975498206002, 51.5884040936899], [-0.819793038416411, 51.58838379523831], [-0.819648800144719, 51.588268149727725], [-0.819147039514258, 51.58763368041164], [-0.818936357700162, 51.58740856531028], [-0.818746726401906, 51.58698404449756], [-0.818744261992127, 51.58691208559554], [-0.818786542624285, 51.586561832259704], [-0.818798419467402, 51.58638211648965], [-0.818772601288344, 51.58593047014409], [-0.818622316220491, 51.585380460186784], [-0.818493730937228, 51.58471737211902], [-0.818460174123387, 51.58461992329734], [-0.818316632401033, 51.58436670899539], [-0.818092592164715, 51.58404524580265], [-0.817774082795079, 51.58341531231479], [-0.817553796530972, 51.58311636524129], [-0.817445537064436, 51.58300557512581], [-0.81689125274485, 51.582562990062094], [-0.816348587477007, 51.58217357091916], [-0.816214612729635, 51.58210837837547], [-0.816144330954904, 51.58209148442308], [-0.816059129697027, 51.58209332267878], [-0.815742672724781, 51.5820523651453], [-0.815217212787465, 51.58194545592985], [-0.815002344756643, 51.58188304223224], [-0.814826435408141, 51.5818210214772], [-0.814485295861823, 51.581730356582035], [-0.81444044205173, 51.581790148477246], [-0.813566956536122, 51.581635656767844], [-0.813282023122958, 51.58154825394594], [-0.813052037332166, 51.581456910143594], [-0.812870182657061, 51.581346270732034], [-0.812790810356639, 51.58129061819263], [-0.812604263086127, 51.581138568732094], [-0.812386509085548, 51.58085492331321], [-0.812249092317067, 51.58070067277682], [-0.812167619564544, 51.58055957666617], [-0.812051961231651, 51.58029045130041], [-0.812042798783181, 51.580254391465495], [-0.812034433182585, 51.580187767648724], [-0.812033188044369, 51.58012481260171], [-0.812058064730261, 51.580000977815104], [-0.81229781440549, 51.579440518478464], [-0.812247901650195, 51.57941753400863], [-0.812446355706805, 51.57916777205078], [-0.81277233113049, 51.578787120404634], [-0.813191367818544, 51.57821318632392], [-0.813476583358647, 51.57767926033569], [-0.81372993476285, 51.57714950751218], [-0.813894821439398, 51.57669169369027], [-0.813998634879093, 51.57614064720803], [-0.814138744624452, 51.57508193151095], [-0.814120940539715, 51.57493428638376], [-0.81419874804071, 51.57321943977239], [-0.814492542549023, 51.572575897898666], [-0.814547744449169, 51.57211787423915], [-0.814549364628156, 51.57200009817749], [-0.81449843948966, 51.57157247390041], [-0.81447275908744, 51.57022974030085], [-0.814470739157426, 51.56964165674743], [-0.814429823166273, 51.56938407823071], [-0.814416473674158, 51.5693425811485], [-0.814362577800679, 51.569251219648685], [-0.81406844007837, 51.5689659058573], [-0.813908308238922, 51.56874308971205], [-0.813853532979604, 51.56863013877188], [-0.81375715260415, 51.56839717647337], [-0.81371284208748, 51.56815934527622], [-0.813667666933597, 51.567788426746645], [-0.813915408556528, 51.567527470860746], [-0.814280993234198, 51.56684059466537], [-0.814704823511867, 51.56613272518902], [-0.814994772052415, 51.56569055892733], [-0.815202529336662, 51.56519091397622], [-0.815219131565213, 51.56505170869626], [-0.815351636137841, 51.564671793906264], [-0.815511116853932, 51.56436408558186], [-0.815565592175225, 51.5642108755608], [-0.815662011458114, 51.564053592855515], [-0.815731002538058, 51.56389693250399], [-0.815762954470268, 51.56377766396064], [-0.815882314039101, 51.56307031436626], [-0.815899206541573, 51.563030920866616], [-0.815918960262566, 51.5629924554096], [-0.816100387305838, 51.56272812828101], [-0.816239061970809, 51.562387838558415], [-0.816351227865814, 51.56217946080997], [-0.816402225233145, 51.562104443964415], [-0.816445731089239, 51.56204014171416], [-0.816755073070336, 51.56173843863565], [-0.81682883069147, 51.5615089920632], [-0.81709149295673, 51.56100450196165], [-0.817126546068515, 51.5610435199925], [-0.817036868291134, 51.56144185218239], [-0.817602599672938, 51.56148891511105], [-0.817650902097453, 51.56129517879352], [-0.81772376875861, 51.56109989153342], [-0.818544773098936, 51.56114952106405], [-0.818940645385275, 51.56112652991029], [-0.819286252234888, 51.56087194248433], [-0.820505192935939, 51.560762809232635], [-0.821152924392013, 51.56109751750433], [-0.821401734784996, 51.56112699167495], [-0.821468077854438, 51.561127657861135], [-0.821837057068471, 51.56108460493917], [-0.822551882107614, 51.56083281445182], [-0.822991148836609, 51.56069335159132], [-0.823298868990794, 51.56056425765945], [-0.823427756279142, 51.560489119484224], [-0.823816316952276, 51.56018999039105], [-0.823841530161624, 51.56016326766115], [-0.823879440321637, 51.5600917131651], [-0.823939663402364, 51.55926686943448], [-0.824000930577316, 51.55924050793579], [-0.823876052668265, 51.55798579953388], [-0.823797968341331, 51.557154174451156], [-0.823813910366727, 51.55698348995032], [-0.824268965579194, 51.556733581409404], [-0.824612742777356, 51.55649154853787], [-0.826344281900068, 51.55484359477631], [-0.826657025878971, 51.55457337099326], [-0.827233265990788, 51.554040521447654], [-0.827929376968602, 51.55344502444081], [-0.828029419518581, 51.553368693973354], [-0.828246668974642, 51.55322159923911], [-0.828485269800709, 51.5530855074047], [-0.828605105979095, 51.55302466009866], [-0.829372820563392, 51.55267264797847], [-0.830416538296143, 51.552300003050114], [-0.830341853307235, 51.55217517181714], [-0.830015969895102, 51.55217192349864], [-0.82991663595149, 51.55216463892005], [-0.829524156953837, 51.55211486704629], [-0.829282415323888, 51.552092673855796], [-0.828838545910614, 51.55207835426137], [-0.828599504055965, 51.55206338004898], [-0.828680519991051, 51.55199764928292], [-0.828534433277995, 51.55195752639793], [-0.828384829125967, 51.55188589688778], [-0.827987906418731, 51.55167242458396], [-0.827957477145366, 51.551621766572], [-0.827834232346298, 51.55159086272802], [-0.827633977763617, 51.551525919798664], [-0.827259642164726, 51.55144395104261], [-0.826879262755715, 51.5513727108443], [-0.826619197529414, 51.551334143723224], [-0.825020961294276, 51.55117248609027], [-0.825087548806697, 51.55099511486544], [-0.825080332863119, 51.55093929339289], [-0.825041927300218, 51.55086247849746], [-0.82502361605882, 51.55078946149474], [-0.825044884876699, 51.55069166368164], [-0.825058487270179, 51.550667521970404], [-0.82509857658956, 51.550622964257876], [-0.825145597538488, 51.55058926609727], [-0.825325151596322, 51.5505056412203], [-0.824909915146001, 51.550164291306395], [-0.82525013660448, 51.550002248032946], [-0.825332063085088, 51.549957209837], [-0.825638185418255, 51.54977504214384], [-0.82578350044032, 51.54967668704559], [-0.825923116734683, 51.54957557721289], [-0.826170603160388, 51.54937393870707], [-0.826371785812396, 51.54917813090106], [-0.826492161334072, 51.549039961540565], [-0.826611394860751, 51.548890091280356], [-0.82670116630089, 51.54876420424681], [-0.826782355507479, 51.54863553380353], [-0.826823019292071, 51.54856850170786], [-0.827062757600335, 51.548106920435835], [-0.827101368877001, 51.54800749719491], [-0.827194201898492, 51.54770629998806], [-0.827307057244965, 51.54741159704493], [-0.827582602426052, 51.54684696658141], [-0.827901482950928, 51.54639156924703], [-0.828226190833503, 51.54604592938406], [-0.828636291311094, 51.545744301711125], [-0.82879259021163, 51.54566673370386], [-0.828904643767125, 51.545627388779295], [-0.829074413403866, 51.54558682127211], [-0.829642313890905, 51.54554213206049], [-0.831137752395514, 51.5457125947128], [-0.831296230672639, 51.545718668970665], [-0.831381409559054, 51.545715021306385], [-0.831520416985355, 51.54569302674314], [-0.831658465956176, 51.54565213966273], [-0.832162223002951, 51.54540897973132], [-0.832259763552867, 51.545373083913695], [-0.832342495362879, 51.54535232675641], [-0.832449690786029, 51.545333611293344], [-0.832560958333776, 51.54532482723805], [-0.832748224427793, 51.54533298427906], [-0.833594396864807, 51.5455158387706], [-0.833803468208608, 51.5455736658201], [-0.834066591719905, 51.54566080332341], [-0.834319302360527, 51.54576042533909], [-0.834632701147966, 51.545911902800114], [-0.834743597643834, 51.54597414830218], [-0.834972095381443, 51.546118487532766], [-0.835101437527799, 51.546192605160904], [-0.835308853525451, 51.54642845092972], [-0.835597379030446, 51.546650714213946], [-0.835675530378703, 51.54669644865621], [-0.835765285050956, 51.54673960061442], [-0.835946924814448, 51.54679895002405], [-0.836738145066418, 51.54693268079673], [-0.837616543696299, 51.54698454508781], [-0.837668585980954, 51.546979665457], [-0.837867989708348, 51.546964555807335], [-0.83797367625272, 51.54694851789646], [-0.838105566972017, 51.54692284834119], [-0.838233315283634, 51.54688994418392], [-0.838329229403341, 51.54686122067977], [-0.838482151664417, 51.546802489195365], [-0.839036827411148, 51.54642762424692], [-0.83955396219585, 51.54605418383688], [-0.840116011657223, 51.54567219327131], [-0.840736658187885, 51.54525391246058], [-0.840941426195899, 51.54514083988849], [-0.841049279937165, 51.54509604683764], [-0.841194847094701, 51.5450426342828], [-0.841299520083949, 51.545009498946165], [-0.841898633062652, 51.544870644649805], [-0.842282244574123, 51.54481328536381], [-0.842762474116396, 51.54475597844143], [-0.843565178344648, 51.54477737891959], [-0.844086979548171, 51.54484366398717], [-0.845732620874625, 51.545006428046236], [-0.847066038668597, 51.54519847249473], [-0.847735465249349, 51.545415455459946], [-0.84773690700669, 51.54541546961599], [-0.848634731053663, 51.54572550693968], [-0.849946581441456, 51.54620234699594], [-0.850255922443757, 51.54628810259981], [-0.850450510421732, 51.5463493547082], [-0.850677807173489, 51.54642891042725], [-0.850994850981171, 51.546552505063595], [-0.852786328510159, 51.547342426670774], [-0.85300310746802, 51.547496506935786], [-0.853571212311598, 51.54778799706584], [-0.854020445083318, 51.54798930492918], [-0.854581807452525, 51.548205193183556], [-0.85478620907829, 51.54827822314386], [-0.854963577157503, 51.54833660211785], [-0.855161244465997, 51.54839068294659], [-0.855663052553423, 51.5485088736389], [-0.856166529832507, 51.54867563412252], [-0.856279761006, 51.54870371304979], [-0.856530960852844, 51.54875111981111], [-0.856804082121709, 51.54878705024175], [-0.857112033920981, 51.54881432744164], [-0.857381573472553, 51.548820548731904], [-0.857549123049589, 51.54881049056259], [-0.857844479857821, 51.548764809719025], [-0.858057604662229, 51.54872012637499], [-0.858337617310588, 51.54865361386837], [-0.858552497890933, 51.54859635820055], [-0.859049392399301, 51.548450127924276], [-0.859439117962245, 51.5483792842905], [-0.859697682244768, 51.54836291412634], [-0.859779823131423, 51.54836551056866], [-0.860170027263243, 51.548390881318824], [-0.860456238387038, 51.54842243448379], [-0.861009536104037, 51.548500639150554], [-0.861158816462501, 51.54852906321894], [-0.861352413335375, 51.54857320311727], [-0.861544411683264, 51.54862362144007], [-0.861731905414109, 51.5486811891874], [-0.862073439754406, 51.548807688741114], [-0.86254414698236, 51.54901726458452], [-0.86282747298222, 51.54916477803516], [-0.863015068128655, 51.549276295417606], [-0.863120154717426, 51.549342054318615], [-0.863285809311142, 51.54946594721784], [-0.863418211576468, 51.54959311456195], [-0.863530092020445, 51.54973357070672], [-0.863653360363745, 51.549937978681236], [-0.863733641061499, 51.550131180181424], [-0.863790336700716, 51.5503448343906], [-0.863824973966162, 51.550691353426465], [-0.863841717303786, 51.55100353042492], [-0.863889686614544, 51.55116225019172], [-0.863971345177536, 51.55135816235228], [-0.864192531443895, 51.5518009005184], [-0.864636908176204, 51.552606367995786], [-0.864885616784935, 51.55304487507487], [-0.864984710926909, 51.55317801261536], [-0.865042267578469, 51.5532415116859], [-0.86524661818949, 51.553434113390985], [-0.866448255146234, 51.55464702695421], [-0.866553639357719, 51.55475954277147], [-0.86713929523259, 51.555454866418074], [-0.86728119491555, 51.55560729781788], [-0.867357192432134, 51.55568356216347], [-0.867846797612144, 51.55612348793147], [-0.867948777192819, 51.5561991033314], [-0.868039688780355, 51.55625572917072], [-0.868162193200772, 51.55631805467487], [-0.868334859667638, 51.55639345221028], [-0.868507816648064, 51.556457162985154], [-0.868686743286876, 51.556512838449954], [-0.868885971532242, 51.55656421341042], [-0.869870431325262, 51.55700170522159], [-0.870538382015661, 51.557289577406756], [-0.87065953954758, 51.55734829060127], [-0.870968323528286, 51.55751940747054], [-0.871084688448939, 51.55759695683154], [-0.871173919806704, 51.55766345498963], [-0.871421843971099, 51.55790412108149], [-0.871517352960249, 51.558008444760674], [-0.871560414081151, 51.55807539786269], [-0.871689483441928, 51.558339198343305], [-0.871715457861831, 51.558455441841], [-0.871819115572425, 51.558871857901316], [-0.872116688986198, 51.559438501590606], [-0.87224925001588, 51.559678057158926], [-0.872395481580982, 51.5598898694294], [-0.872503199025109, 51.56002578071336], [-0.872616797176149, 51.56015725249727], [-0.872767427555836, 51.560307962534345], [-0.873712440787397, 51.560887112412736], [-0.874019840047307, 51.56105730856152], [-0.874413245272104, 51.56124990894107], [-0.875159636577155, 51.56157807024888], [-0.875469862748955, 51.561692540724444], [-0.875618818634067, 51.561736229028845], [-0.87605776549836, 51.56183754414599], [-0.876366373823489, 51.56190074371407], [-0.87668521195865, 51.561958645316366], [-0.876968369886447, 51.56200091867397], [-0.877197163756339, 51.56202468778881], [-0.877516822654725, 51.5620493254706], [-0.878098847659676, 51.56208186543848], [-0.879220205739232, 51.56212315048378], [-0.879742407250114, 51.562124539056946], [-0.8803196812009, 51.56211566057546], [-0.88047709073344, 51.562109069498774], [-0.880781923890852, 51.5620912956137], [-0.881058065229737, 51.56206695318683], [-0.881262135404653, 51.56204012483733], [-0.881418631531618, 51.562011943580096], [-0.881649213834679, 51.56196288783239], [-0.882003250428868, 51.56187993977645], [-0.882376388095226, 51.56178278572967], [-0.882764299497775, 51.56167138432022], [-0.883194585739291, 51.561537905452155], [-0.883498576630078, 51.5614364931922], [-0.883756281221724, 51.56134003501421], [-0.884086956514705, 51.56120920221847], [-0.884407665982054, 51.56107287871968], [-0.885457870533961, 51.5606602434964], [-0.885606584631741, 51.56059601575753], [-0.88578613479336, 51.56050960117672], [-0.88595858283258, 51.56041862303493], [-0.886091760097573, 51.560340759581955], [-0.886521253482028, 51.56006159461465], [-0.886883345687433, 51.559824949307504], [-0.887389733197074, 51.559468282123994], [-0.887580604245187, 51.55927227271282], [-0.887708691217493, 51.559107139161064], [-0.888191015763102, 51.55861266662589], [-0.888192457957821, 51.55861268028735], [-0.888287760756616, 51.55849039596854], [-0.888562664893145, 51.558278995716826], [-0.888677108662246, 51.55814070717499], [-0.888887527216288, 51.55791161122132], [-0.889030158653071, 51.55774121885465], [-0.889203011709727, 51.557514464273005], [-0.88945350380216, 51.55717964382524], [-0.889600043815338, 51.556967026485395], [-0.889736358446423, 51.55670036168757], [-0.889858423154084, 51.55648571413559], [-0.889957478394766, 51.55626815130874], [-0.88999480235023, 51.556157006507235], [-0.890171676314085, 51.5555274577222], [-0.89040150985715, 51.554855248874496], [-0.891496918227321, 51.55316795557777], [-0.892148150167392, 51.55212836271704], [-0.892302414411729, 51.5517746441604], [-0.892679622502675, 51.55097074336506], [-0.892968782461996, 51.55046633574481], [-0.893655961353766, 51.54930928124159], [-0.894173495307085, 51.548604708475054], [-0.894625223197592, 51.54805597035748], [-0.895275002004698, 51.547308578527485], [-0.895581553072133, 51.54691762432374], [-0.89567259455686, 51.54679079793778], [-0.895853734572665, 51.54651645501248], [-0.896746527292651, 51.54508077321264], [-0.896901839906062, 51.544860136456265], [-0.897255891587921, 51.54494888564188], [-0.898115394177492, 51.545187148067974], [-0.89822539056202, 51.54523044209882], [-0.898280925208036, 51.54525973711452], [-0.898317695099433, 51.54528975512979], [-0.898473190139101, 51.54530020648797], [-0.899619101095523, 51.54544313546729], [-0.901006352145532, 51.54562517973134], [-0.901825064903737, 51.54570208025918], [-0.903152296119515, 51.54586195719927], [-0.903215411109321, 51.54587603470979], [-0.903297183099556, 51.545893883241035], [-0.903017403675823, 51.54655396139701], [-0.902616538461231, 51.54745118838641], [-0.902395351374664, 51.54801290343283], [-0.902224993241029, 51.54837997226972], [-0.902166365216381, 51.54859882282856], [-0.902146545476892, 51.54876318664974], [-0.902208891983797, 51.54892921824649], [-0.902230894835185, 51.549033728477525], [-0.902380735585514, 51.54999904656443], [-0.902673692529055, 51.551610411429984], [-0.902806620567669, 51.55250003963136], [-0.902816651036337, 51.55268266601908], [-0.902804979597758, 51.55292803179951], [-0.902756997764027, 51.55318384821566], [-0.902654701329301, 51.55353896524768], [-0.902427419928527, 51.55423280167036], [-0.901847751389036, 51.555843197130926], [-0.901770133963284, 51.556011515784924], [-0.90172081219273, 51.556082988265544], [-0.902203201151337, 51.55617741874876], [-0.906010818638152, 51.55679743475821], [-0.906204734572883, 51.556771367660915], [-0.906311884484255, 51.55675438267928], [-0.906358975073264, 51.55689599188705], [-0.90638590166065, 51.55691602462513], [-0.906615524939952, 51.55696492104195], [-0.907278267614382, 51.55705831304524], [-0.907858939004531, 51.55720578727805], [-0.908174621569804, 51.5572752632291], [-0.908339760107985, 51.55730647205926], [-0.908420198492941, 51.557320707807335], [-0.908923514869315, 51.557385632424236], [-0.909322889103033, 51.5574540848968], [-0.909506497855653, 51.557497152980005], [-0.909658336207082, 51.557541723987356], [-0.909754316957776, 51.55756959081508], [-0.909871264277585, 51.557625526703404], [-0.91002439519459, 51.55767640345812], [-0.91014009429463, 51.557724234915874], [-0.910363938659539, 51.55783421414451], [-0.910769828656022, 51.55805288423369], [-0.910899633150579, 51.55817457810929], [-0.911134211672749, 51.55843931338367], [-0.911191431662737, 51.558519870694006], [-0.911262279817118, 51.55863382382822], [-0.911345597527786, 51.558829717406], [-0.911347039740573, 51.55882973078292], [-0.911354152248679, 51.55895478193117], [-0.911501156990394, 51.55932300828838], [-0.911744284152759, 51.55977395051585], [-0.911784714781588, 51.55983277167005], [-0.911848028397377, 51.559899897522016], [-0.911951124589094, 51.55999256899618], [-0.912043240814804, 51.56006176008463], [-0.912343203354435, 51.560247072388215], [-0.91252735254784, 51.560389049785144], [-0.912663001694362, 51.56050809825428], [-0.913644040185922, 51.561715780647766], [-0.914729932802775, 51.56149024267299], [-0.915936249212879, 51.561720788632314], [-0.917099342153636, 51.56255696600573], [-0.917716153850379, 51.56209778262442], [-0.918510926061236, 51.561494569835055], [-0.919190082061234, 51.56187308324347], [-0.919265061021478, 51.56187467281339], [-0.919319677161742, 51.561883268238525], [-0.919395565855925, 51.56190734542486], [-0.919450182060758, 51.561915940789575], [-0.919451624375034, 51.56191595406745], [-0.919612764956992, 51.561873377936905], [-0.919684473323871, 51.562134798108005], [-0.919731696029427, 51.56227280618616], [-0.919795125391412, 51.562396576626476], [-0.919877773921036, 51.56250074208832], [-0.920024454979703, 51.56264236275641], [-0.920273793481437, 51.562835281288635], [-0.920455811890565, 51.56300779833283], [-0.92054707457537, 51.56311384087594], [-0.920622684574115, 51.56321074767881], [-0.920768557091237, 51.56344767224896], [-0.920815933327546, 51.56357938704838], [-0.92087756775181, 51.56365728255545], [-0.920897207761338, 51.56368084160506], [-0.920950783915168, 51.5637334861553], [-0.921113636188415, 51.563862665653886], [-0.921195103215065, 51.56395602921301], [-0.921351882125165, 51.56422002842434], [-0.921395592904906, 51.56432383493418], [-0.921420893006769, 51.56441308549212], [-0.921438619701684, 51.56451755238376], [-0.921413985191466, 51.56464410933019], [-0.92167481147157, 51.56453500840272], [-0.923479752756133, 51.56373602685831], [-0.924155377789785, 51.5634113259626], [-0.924873111301507, 51.56399247344971], [-0.924951149378637, 51.56404803772923], [-0.925274524870032, 51.56428388466152], [-0.92664770764064, 51.56515336065692], [-0.927393574791524, 51.56558368874587], [-0.927927453464337, 51.5659491340051], [-0.92835633038721, 51.56599441223158], [-0.928244524844939, 51.56725223174753], [-0.928125342518282, 51.56741209518939], [-0.9281054698832, 51.56745956981058], [-0.927839528670842, 51.568092855254655], [-0.93052584537502, 51.568532778436946], [-0.930434864440691, 51.56872077542371], [-0.930290537945866, 51.568968530677175], [-0.93032458943141, 51.568993118606556], [-0.930407160146686, 51.56904062792432], [-0.930494248489543, 51.56908008580425], [-0.93056005729139, 51.5691040638391], [-0.930644619283805, 51.56912821268005], [-0.930755210497826, 51.569149901051155], [-0.931002865082897, 51.56917193845677], [-0.931506345671459, 51.5692367670135], [-0.9319179887186, 51.56928007686346], [-0.93222706378822, 51.569328746497334], [-0.932389335623021, 51.56936169347699], [-0.932508352034028, 51.56939334769875], [-0.932608510110525, 51.56942932617174], [-0.932871163025979, 51.56955040480943], [-0.933086053531851, 51.569616199317096], [-0.933414865356232, 51.569684826955594], [-0.93386895042082, 51.56976448240679], [-0.934635570791928, 51.5700537823344], [-0.934785550106084, 51.57011898456062], [-0.934985327167408, 51.57021431104797], [-0.935122135234023, 51.5702874858722], [-0.935333952219633, 51.570423383659445], [-0.935683954424458, 51.570697207984594], [-0.935948857561102, 51.57084617434456], [-0.936321009353978, 51.57109861741231], [-0.936776166293208, 51.57138058458356], [-0.936874033955222, 51.57139136186155], [-0.937185388877335, 51.57140497135624], [-0.937358713203379, 51.57139754881963], [-0.937474500211682, 51.571382411871916], [-0.937604964789291, 51.57135661760672], [-0.938082770631526, 51.571224267138255], [-0.938303744516349, 51.57115343316592], [-0.938622419406116, 51.57103852370375], [-0.938583754985448, 51.57127375671787], [-0.938615426417796, 51.5714008261012], [-0.938651384040882, 51.57152973256723], [-0.938810396393867, 51.572013125409214], [-0.938917102284257, 51.5723881447753], [-0.938977920287056, 51.572564931845086], [-0.939147081147104, 51.57298457478389], [-0.93921529881351, 51.57340150695511], [-0.939295227462625, 51.57374930879435], [-0.939359853061118, 51.57401065198471]]]}}
+ test/json-data/integers.json view
@@ -0,0 +1,1 @@+[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 9, 9, 10, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 26, 27, 29, 31, 33, 35, 37, 39, 42, 44, 47, 50, 53, 56, 60, 63, 67, 72, 76, 81, 86, 91, 96, 102, 109, 115, 122, 130, 138, 146, 155, 165, 175, 186, 197, 209, 222, 236, 250, 266, 282, 299, 318, 337, 358, 380, 403, 428, 454, 482, 511, 543, 576, 611, 649, 688, 730, 775, 823, 873, 926, 983, 1043, 1107, 1175, 1247, 1323, 1404, 1490, 1582, 1679, 1781, 1890, 2006, 2129, 2259, 2398, 2544, 2700, 2865, 3041, 3227, 3425, 3634, 3857, 4093, 4343, 4609, 4891, 5191, 5509, 5846, 6204, 6583, 6986, 7414, 7868, 8349, 8860, 9403, 9978, 10589, 11237, 11925, 12655, 13430, 14252, 15124, 16050, 17032, 18075, 19181, 20355, 21601, 22923, 24326, 25815, 27396, 29072, 30852, 32740, 34744, 36871, 39128, 41523, 44064, 46762, 49624, 52661, 55884, 59305, 62935, 66787, 70875, 75213, 79817, 84702, 89887, 95389, 101227, 107423, 113998, 120976, 128381, 136239, 144578, 153427, 162818, 172784, 183360, 194583, 206493, 219132, 232545, 246778, 261883, 277912, 294923, 312975, 332131, 352460, 374034, 396928, 421223, 447005, 474365, 503400, 534212, 566911, 601610, 638433, 677511, 718980, 762987, 809688, 859247, 911840, 967652, 1026880, 1089734, 1156434, 1227217, 1302333, 1382046, 1466638, 1556408, 1651673, 1752769, 1860052, 1973902, 2094721, 2222934, 2358996, 2503385, 2656613, 2819219, 2991777, 3174898, 3369227, 3575451, 3794297, 4026539, 4272995, 4534536, 4812086, 5106625, 5419191, 5750889, 6102889, 6476435, 6872845, 7293518, 7739939, 8213686, 8716429, 9249944, 9816115, 10416939, 11054539, 11731166, 12449207, 13211198, 14019829, 14877955, 15788605, 16754994, 17780533, 18868844, 20023768, 21249383, 22550016, 23930257, 25394980, 26949356, 28598872, 30349352, 32206975, 34178300, 36270285, 38490317, 40846232, 43346349, 45999492, 48815029, 51802899, 54973651, 58338478, 61909260, 65698602, 69719882, 73987297, 78515911, 83321713, 88421668, 93833782, 99577160, 105672079, 112140056, 119003924, 126287916, 134017747, 142220706, 150925751, 160163614, 169966908, 180370243, 191410345, 203126189, 215559137, 228753081, 242754599, 257613123, 273381107, 290114218, 307871529, 326715729, 346713346, 367934976, 390455540, 414354543, 439716356, 466630515, 495192035, 525501749, 557666661, 591800322, 628023236, 666463282, 707256166, 750545902, 796485316, 845236589, 896971830, 951873682, 1010135966, 1071964368, 1137577163, 1207205986, 1281096650, 1359510014, 1442722903, 1531029087, 1624740315, 1724187420, 1829721484, 1941715077, 2060563573, 2186686548, 2320529259, 2462564214, 2613292844, 2773247272, 2942992191, 3123126858, 3314287206, 3517148098, 3732425698, 3960880011, 4203317554, 4460594215, 4733618266, 5023353573, 5330822998, 5657112012, 6003372524, 6370826950, 6760772526, 7174585891, 7613727944, 8079749004, 8574294281, 9099109685, 9656047991, 10247075377, 10874278366, 11539871197, 12246203633, 12995769265, 13791214310, 14635346955, 15531147272, 16481777734, 17490594386, 18561158687, 19697250088, 20902879371, 22182302812, 23540037202, 24980875800, 26509905246, 28132523526, 29854459026, 31681790754, 33620969802, 35678842122, 37862672691, 40180171161, 42639519077, 45249398761, 48019023960, 50958172379, 54077220194, 57387178688, 60899733121, 64627283986, 68582990784, 72780818484, 77235586822, 81963022620, 86979815309, 92303675844, 97953399235, 103948930895, 110311437058, 117063379497, 124228594829, 131832378662, 139901574895, 148464670491, 157551896043, 167195332496, 177429024407, 188289100133, 199813899374, 212044108527, 225022904322, 238796106249, 253412338321, 268923200725, 285383451995, 302851202324, 321388118716, 341059642687, 361935221296, 384088552321, 407597844432, 432546093294, 459021374572, 487117154867, 516932621682, 548573033590, 582150091830, 617782334651, 655595555791, 695723248569, 738307077168, 783497376747, 831453684183, 882345301285, 936351892486, 993664119121, 1054484312524, 1119027188325, 1187520604468, 1260206365627, 1337341076854, 1419197049486, 1506063262491, 1598246382662, 1696071847252, 1799885012878, 1910052374747, 2026962860500, 2151029203266, 2282689398739, 2422408251457, 2570679015712, 2728025136906, 2895002099486, 3072199387991, 3260242568131, 3459795495242, 3671562657914, 3896291665080, 4134775885316, 4387857247705, 4656429214122, 4941439933460, 5243895588908, 5564863950114, 5905478142772, 6266940648935, 6650527552175, 7057593042589, 7489574197539, 7947996055022, 8434476997558, 8950734465625, 9498591020797, 10079980779998, 10696956243580, 11351695541337, 12046510122031, 12783852913581, 13566326982715, 14396694724673, 15277887615381, 16213016560543, 17205382878181, 18258489953389, 19376055606456, 20562025218016, 21820585657560, 23156180064488, 24573523533875, 26077619762337, 27673778712750, 29367635360200, 31165169585327, 33072727285306, 35097042776985, 37245262571278, 39524970602741, 41944215003394, 44511536515321, 47235998642351, 50127219647252, 53195406507421, 56451390948928, 59906667686130, 63573435001862, 67464637811456, 71594013362620, 75976139732519, 80626487293267, 85561473327514, 90798519986944, 96356115798305, 102253880934088, 108512636478302, 115154477931865, 122202853217119, 129682645456833, 137620260819954, 146043721744222, 154982765864743, 164468950997792, 174535766550465, 185218751749486, 196555621106568, 208586397563259, 221353553785311, 234902162105402, 249280053643550, 264537987166964, 280729828285480, 297912739615178, 316147382581544, 335498131574595, 356033301212013, 377825387512598, 400951323831468, 425492752460545, 451536312853150, 479173947490266, 508503226468250, 539627691953919, 572657223723034, 607708427072674, 644905044476938, 684378392439283, 726267825083707, 770721226121430, 817895530929871, 867957280587026, 921083209817197, 977460870923688, 1037289295911185, 1100779699135317, 1168156222959992, 1239656729054927, 1315533638126921, 1396054821049394, 1481504544536185, 1572184474698156, 1668414742025481, 1770535071555377, 1878905982215138, 1993910059574563, 2115953306501003, 2245466576485316, 2382907094698829, 2528760072151156, 2683540418647385, 2847794560591953]
+ test/json-data/jp10.json view
@@ -0,0 +1,1 @@+{"results":[{"from_user_id_str":"2458313","profile_image_url":"http://a2.twimg.com/profile_images/1203653060/fure091226_normal.png","created_at":"Thu, 27 Jan 2011 20:30:04 +0000","from_user":"19princess","id_str":"30724239224995840","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6d77\u6674\u300c\u672c\u65e5\u306e\u6771\u4eac\u306e\u304a\u5929\u6c17\u306f\u6674\u6642\u3005\u66c7\u3067\u3057\u3087\u3046\u3002\u6700\u9ad8\u6c17\u6e29\u306f8\u5ea6\u3001\u6700\u4f4e\u6c17\u6e29\u306f1\u5ea6\u3067\u3059\u3002\u3042\u306a\u305f\u306e\u4eca\u65e5\u306e\u4eba\u751f\u306b\u3068\u3073\u3063\u304d\u308a\u306e\u304a\u5929\u6c17\u3092\u2665\u300d","id":30724239224995840,"from_user_id":2458313,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://babyprincess.sakura.ne.jp/about/&quot; rel=&quot;nofollow&quot;&gt;\u5929\u4f7f\u5bb6\u306e\u88cf\u5c71&lt;/a&gt;"},{"from_user_id_str":"66578965","profile_image_url":"http://a0.twimg.com/profile_images/1183763267/fossetta_normal.png","created_at":"Thu, 27 Jan 2011 20:00:04 +0000","from_user":"Fossetta_Tokyo","id_str":"30716689779785729","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u304a\u306f\u3088\u3046\u3054\u3056\u3044\u307e\u3059!!\u6771\u4eac\u90fd\u3001\u672c\u65e5\u306e\u304a\u5929\u6c17\u306f\u6674\u6642\u3005\u66c7\u3002\u6700\u9ad8\u6c17\u6e298\u5ea6\u3001\u6700\u4f4e\u6c17\u6e291\u5ea6\u3002\u6771\u4eac\u5730\u65b9\u3067\u306f\u3001\u7a7a\u6c17\u306e\u4e7e\u71e5\u3057\u305f\u72b6\u614b\u304c\u7d9a\u3044\u3066\u3044\u307e\u3059\u3002\u706b\u306e\u53d6\u308a\u6271\u3044\u306b\u6ce8\u610f\u3057\u3066\u4e0b\u3055\u3044\u3002\u4f0a\u8c46\u8af8\u5cf6\u3068\u5c0f\u7b20\u539f\u8af8\u5cf6\u306b\u306f\u3001\u5f37\u98a8\u3001\u6ce2\u6d6a\u3001\u4e7e\u71e5\u3001\u971c\u306e\u6ce8\u610f\u5831\u3092\u767a\u8868\u4e2d\u3067\u3059\u3002","id":30716689779785729,"from_user_id":66578965,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://bit.ly/Fossetta&quot; rel=&quot;nofollow&quot;&gt;\u30d5\u30a9\u30bb\u30c3\u30bf ver.3.1.1&lt;/a&gt;"},{"from_user_id_str":"104041146","profile_image_url":"http://a2.twimg.com/profile_images/863965118/001jwatokyo_normal.jpg","created_at":"Thu, 27 Jan 2011 19:44:33 +0000","from_user":"jwa_tokyo","id_str":"30712787537756160","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6c17\u8c61\u5e81\u304b\u3089\u6771\u4eac\u306e\u5929\u6c17\u4e88\u5831\u304c\u767a\u8868\u3055\u308c\u307e\u3057\u305f\u3000\u305d\u306e\u5929\u6c17\u4e88\u5831\u3092\u3053\u3053\u3067\u3064\u3076\u3084\u3053\u3046\u304b\u306a\u3041\uff5e\uff1f\u3000\u8003\u3048\u4e2d\u3000\u307e\u3066\u6b21\u53f7\uff08\u7b11","id":30712787537756160,"from_user_id":104041146,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.docodemo.jp/twil/&quot; rel=&quot;nofollow&quot;&gt;Twil2 (Tweet Anytime, Anywhere by Mail)&lt;/a&gt;"},{"from_user_id_str":"144500192","profile_image_url":"http://a3.twimg.com/a/1294874399/images/default_profile_3_normal.png","created_at":"Thu, 27 Jan 2011 18:59:33 +0000","from_user":"kyo4to4","id_str":"30701462774358016","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6771\u4eac\u90fd \u516b\u4e08\u5cf6 - \u4eca\u65e5\u306e\u5929\u6c17\u306f\u30fb\u30fb\u30fb\u66c7\u306e\u3061\u6674\u3067\u3059\u306e\uff01","id":30701462774358016,"from_user_id":144500192,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/kyo4to4&quot; rel=&quot;nofollow&quot;&gt;lost-sheep-bot&lt;/a&gt;"},{"from_user_id_str":"165724582","profile_image_url":"http://a0.twimg.com/profile_images/1222842347/163087_1256281064506_1753997852_484926_2761052_n_normal.jpg","created_at":"Thu, 27 Jan 2011 17:39:06 +0000","from_user":"Rifqi_19931020","id_str":"30681213748387840","metadata":{"result_type":"recent"},"to_user_id":113796067,"text":"@CHLionRagbaby \u30b1\u30f3\u3061\u3083\u3093\u3001\u671d\u4eca\u307e\u3067\u306e\u81ea\u5206\u306e\u30db\u30fc\u30e0\u30a8\u30ea\u30a2\u304b\u3089\u307e\u3060\u975e\u5e38\u306b\u5bd2\u3044\u3068\u96e8\u304c\u964d\u3063\u3066\u3044\u305f..\u3069\u306e\u3088\u3046\u306b\u73fe\u5728\u306e\u6771\u4eac\u306e\u5929\u6c17\uff1f","id":30681213748387840,"from_user_id":165724582,"to_user":"CHLionRagbaby","geo":null,"iso_language_code":"ja","to_user_id_str":"113796067","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"120911929","profile_image_url":"http://a0.twimg.com/profile_images/772435076/20100312___capture2_normal.png","created_at":"Thu, 27 Jan 2011 17:28:30 +0000","from_user":"Cirno_fan","id_str":"30678546020040705","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u4eca\u65e5\u306e\u6771\u4eac\u306e\u5929\u6c17\u306f\u3001\u66c7\u6642\u3005\u6674\u3067\u6700\u9ad8\u6c17\u6e29\u306f8\u2103\uff01 \u6700\u4f4e\u6c17\u6e29\u306f2\u2103\u3060\u3063\u305f\u3088\uff01 RT @mimi22999 \uff08\uff65\u2200\uff65\uff09\uff4c\u3001\u865a\u5f31\u306a\u8005\u306b\u3068\u3063\u3066\u3001\u6717\u3089\u304b\u306a\u9854\u306f\u4e0a\u5929\u6c17\u3068\u540c\u3058\u304f\u3089\u3044\u3046\u308c\u3057\u3044\u3082\u306e\u3060\u3002\u30d5\u30e9\u30f3\u30af\u30ea\u30f3 #tenki #bot","id":30678546020040705,"from_user_id":120911929,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://blog.livedoor.jp/fairycirno/archives/34686.html&quot; rel=&quot;nofollow&quot;&gt;\u5e7b\u60f3\u90f7 \u9727\u306e\u6e56&lt;/a&gt;"},{"from_user_id_str":"65976527","profile_image_url":"http://a0.twimg.com/profile_images/452810990/little_italies_____normal.jpg","created_at":"Thu, 27 Jan 2011 17:00:03 +0000","from_user":"heta_weather01","id_str":"30671388754845696","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3010\u30d8\u30bf\u5929\u3011Tere hommikust.\u30a8\u30b9\u30c8\u30cb\u30a2\u3067\u3059\u3002\u4eca\u65e5\u306e\u6771\u4eac\u306e\u5929\u6c17\u306f\u6674\u6642\u3005\u66c7\u3067\u660e\u65e5\u306f\u66c7\u308a\u3067\u3059\u3002\u3061\u306a\u307f\u306b\u50d5\u306e\u3068\u3053\u308d\u3067\u306f\u66c7\u308a\u3067\u6c17\u6e29-7\u2103\u3067\u3059\u3002\u3061\u306a\u307f\u306b\u30b9\u30ab\u30a4\u30d7\u306e\u958b\u767a\u672c\u90e8\u306f\u50d5\u306e\u5bb6\u306b\u3042\u308b\u3093\u3067\u3059\u3002","id":30671388754845696,"from_user_id":65976527,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www15.atpages.jp/~kageyanma/&quot; rel=&quot;nofollow&quot;&gt;\u5730\u7403\u306e\u4e2d&lt;/a&gt;"},{"from_user_id_str":"7278433","profile_image_url":"http://a3.twimg.com/profile_images/1218965303/michael-1_normal.png","created_at":"Thu, 27 Jan 2011 16:50:16 +0000","from_user":"shimohiko","id_str":"30668926035689472","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3042\u308a\u3002\n\u3010\u7f8e\u4eba\u5929\u6c17/\u6771\u4eac\u3011\u7f8e\u4eba\u5929\u6c17\u30ad\u30e3\u30b9\u30bf\u30fc\u306e&quot;\u3055\u3042\u3084\u3093\u3055\u3093&quot;\u306b\u3088\u308b\u3068\u300c1/29(\u571f)\u306f\u304f\u3082\u308a\u3067\u3001\u964d\u6c34\u78ba\u738740%\u3001\u6700\u9ad8\u6c17\u6e29\u306f6\u2103\u3067\u6700\u4f4e\u6c17\u6e29\u306f1\u2103\u3067\u3059\u300d\u7f8e\u4eba\u5929\u6c17\u21d2http://bit.ly/djB8th http://twitpic.com/3twnl0 #bt_tenki","id":30668926035689472,"from_user_id":7278433,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://bijintenki.jp&quot; rel=&quot;nofollow&quot;&gt;bijintenki.jp&lt;/a&gt;"},{"from_user_id_str":"61770","profile_image_url":"http://a3.twimg.com/profile_images/1206955079/tw172a_normal.png","created_at":"Thu, 27 Jan 2011 16:40:52 +0000","from_user":"rsky","id_str":"30666561853333504","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3075\u3068\u591c\u7a7a\u3092\u898b\u4e0a\u3052\u3066\u30aa\u30ea\u30aa\u30f3\u304c\u898b\u3048\u306a\u304f\u3066\u300c\u6771\u4eac\u306b\u306f\u7a7a\u304c\u306a\u3044\u300d\u3068\u667a\u6075\u5b50\u306e\u3088\u3046\u306a\u3053\u3068\u3092\u601d\u3063\u305f\u308f\u3051\u3060\u304c\u3001\u305f\u3076\u3093\u534a\u5206\u3050\u3089\u3044\u306f\u5929\u6c17\u306e\u305b\u3044\u3067\u3059","id":30666561853333504,"from_user_id":61770,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;"},{"from_user_id_str":"103259265","profile_image_url":"http://a0.twimg.com/profile_images/767857973/anime_icon_normal.gif","created_at":"Thu, 27 Jan 2011 16:37:42 +0000","from_user":"liveshowonly","id_str":"30665765698928640","metadata":{"result_type":"recent"},"to_user_id":null,"text":"iPhone\u5929\u6c17\u3002\u6771\u4eac4\u2103\u3063\u3066\u7d50\u69cb\u5bd2\u304f\u306a\u3044\u3058\u3083\u3093\u3002\uff08\u78ba\u304b\u306b\u3082\u306e\u51c4\u304f\u5bd2\u3044\u8a33\u3067\u306f\u7121\u3044\uff09\u3002\u798f\u5ca1\u5e02\u306f1\u2103\u3060\u3002\u52dd\u3061\u3060\u305c\u3002Rock'n'roll","id":30665765698928640,"from_user_id":103259265,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"165242761","profile_image_url":"http://a1.twimg.com/profile_images/1147334744/SN3E00600001_normal.jpg","created_at":"Thu, 27 Jan 2011 16:37:35 +0000","from_user":"deuxavril0502","id_str":"30665736556912640","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6b8b\u5ff5\u3001\u79c1\u306f\u6c96\u7e04\u3067\u3059(&gt;_&lt;)\u3067\u3082\u304a\u5929\u6c17\u826f\u3055\u305d\u3046\u3067\u826f\u304b\u3063\u305f\u306d\u3002\u3053\u3061\u3089\u306f\u96e8\u3088\uff5eRT @rinandy2010: \u305d\u3046\u3067\u3059\u3063\u266a\u51fa\u5f35\u3067(^^)\u304a\u306d\u3048\u3055\u307e\u306f\u6771\u4eac\u3067\u3059\u304b\uff1f\u3081\u3063\u3061\u3083\u5929\u6c17\u826f\u304f\u3066\u3073\u3063\u304f\u308a\u3067\u3059\u3002\u624b\u888b\u3068\u304b\u5168\u7136\u3044\u3089\u306a\u3044\u3067\u3059\u306d\u30fc RT @deuxavril0502 \u6771\u4eac\u306a\u306e\u30fc\uff1f","id":30665736556912640,"from_user_id":165242761,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.echofon.com/&quot; rel=&quot;nofollow&quot;&gt;Echofon&lt;/a&gt;"},{"from_user_id_str":"163900288","profile_image_url":"http://a1.twimg.com/profile_images/1210224823/icon12945064955238_normal.jpg","created_at":"Thu, 27 Jan 2011 16:24:37 +0000","from_user":"nyao_yurichan","id_str":"30662472734089216","metadata":{"result_type":"recent"},"to_user_id":100985873,"text":"@ray_ko302 \u767a\u898b\u3042\u308a\u304c\u3068\u3067\u3059\u3045\u3002\u3053\u3061\u3089\u3067\u3082\u3088\u308d\u3057\u304f\u306d\u3002\u6771\u4eac\u306f\u4eca\u65e5\u3082\u4e7e\u71e5\u3067\u5927\u5909\u3088\u3002\u305d\u3063\u3061\u3068\u771f\u9006\u306e\u5929\u6c17\u3060\u306d\u3002\u30a2\u30a4\u30b9\u30d0\u30fc\u30f3\u304d\u3092\u3064\u3051\u3066\u3088\u306d\u3002","id":30662472734089216,"from_user_id":163900288,"to_user":"ray_ko302","geo":null,"iso_language_code":"ja","to_user_id_str":"100985873","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"49227098","profile_image_url":"http://a3.twimg.com/profile_images/965007882/____normal.jpg","created_at":"Thu, 27 Jan 2011 15:52:26 +0000","from_user":"dosannko6","id_str":"30654371016478720","metadata":{"result_type":"recent"},"to_user_id":108570870,"text":"@peke_hajiP \u4ffa\u304c\u6771\u4eac \u6765\u3066\u6700\u521d\u306b\u9a5a\u3044\u305f\u306e\u304c\u3001\u5929\u6c17\u4e88\u5831\u3067\u82b1\u7c89\u60c5\u5831\u304c\u6d41\u308c\u308b\u3053\u3068\u3067\u3001\u6700\u521d\u306b\u8a66\u3057\u305f\u306e\u304c \u30b4\u30ad\u69d8 \u53ec\u559a\u306e\u5100\u5f0f\u3067\u3059\u304a\uff1f","id":30654371016478720,"from_user_id":49227098,"to_user":"peke_hajip","geo":null,"iso_language_code":"ja","to_user_id_str":"108570870","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"71148934","profile_image_url":"http://a0.twimg.com/profile_images/1209223630/image_normal.jpg","created_at":"Thu, 27 Jan 2011 15:46:49 +0000","from_user":"123keiko","id_str":"30652958311981058","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u9727\u5cf6\u306e\u964d\u7070\u306e\u30cb\u30e5\u30fc\u30b9\u3092\u898b\u3066\u601d\u3044\u51fa\u3057\u307e\u3057\u305f\u3002\u4e0a\u4eac\u3057\u305f\u59cb\u3081\u306e\u9803\u3001\u6771\u4eac\u306f1\u5e74\u4e2d\u3001\u7070\u304c\u964d\u3089\u306a\u3044\u304b\u3089\u7a7a\u6c17\u304c\u6f84\u3093\u3067\u3066\u904e\u3054\u3057\u3084\u3059\u3044\u306a\u3041\u3001\u3068\u601d\u3063\u305f\u306a\u3041\u3001\u3063\u3066\u3002\n\u5b9f\u5bb6\u306f\u51ac\u306e\u5b63\u7bc0\u98a8\u3067\u685c\u5cf6\u306e\u7070\u304c\u964d\u308b\u5730\u533a\u3067\u3057\u305f\u3002\u9e7f\u5150\u5cf6\u306e\u5929\u6c17\u4e88\u5831\u3067\u306f\u3001\u6bce\u65e5\u3001\u685c\u5cf6\u4e0a\u7a7a\u306e\u98a8\u5411\u304d\u4e88\u5831\u304c\u3067\u307e\u3059\u3002\u3053\u308c\u304b\u3089\u306f\u9727\u5cf6\u3082\u304b\u306a\uff1f","id":30652958311981058,"from_user_id":71148934,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"91124773","profile_image_url":"http://a3.twimg.com/profile_images/1139308356/prof101007_3-1_normal.jpg","created_at":"Thu, 27 Jan 2011 15:43:35 +0000","from_user":"sanposuruhito","id_str":"30652146277945345","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u4eca\u5bb5\u306e\u90fd\u5fc3\u306f\u3001\u8eab\u3082\u5f15\u304d\u7de0\u307e\u308b\u3068\u3044\u3046\u3088\u308a\u306f\u51cd\u3048\u308b\u7a0b\u306e\u51b7\u6c17\u3092\u611f\u3058\u308b\u5bd2\u3044\u591c\u3067\u3059\u3002\u660e\u65e5\u65e5\u4e2d\u306e\u6771\u4eac\u306e\u5929\u6c17\u306f\u3001\u5915\u65b9\u306b\u591a\u5c11\u96f2\u304c\u51fa\u308b\u5834\u6240\u304c\u3042\u308a\u305d\u3046\u306a\u3082\u306e\u306e\u6982\u306d\u6674\u308c\u7a7a\u304c\u5e83\u304c\u308a\u7d9a\u3051\u305d\u3046\u3067\u3059\u3002\u6700\u9ad8\u6c17\u6e29\u306f\u30015-6\u5ea6\u4f4d\u3068\u306a\u308a\u305d\u3046\u3067\u3059\u3002#weather_tokyo","id":30652146277945345,"from_user_id":91124773,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://projects.playwell.jp/go/Saezuri&quot; rel=&quot;nofollow&quot;&gt;Saezuri&lt;/a&gt;"}],"max_id":30724239224995840,"since_id":28179956995457024,"refresh_url":"?since_id=30724239224995840&q=%E6%9D%B1%E4%BA%AC%E3%80%80%E5%A4%A9%E6%B0%97","next_page":"?page=2&max_id=30724239224995840&lang=ja&q=%E6%9D%B1%E4%BA%AC%E3%80%80%E5%A4%A9%E6%B0%97","results_per_page":15,"page":1,"completed_in":0.104827,"warning":"adjusted since_id to 28179956995457024 (), requested since_id was older than allowed -- since_id removed for pagination.","since_id_str":"28179956995457024","max_id_str":"30724239224995840","query":"%E6%9D%B1%E4%BA%AC%E3%80%80%E5%A4%A9%E6%B0%97"}
+ test/json-data/jp100.json view
@@ -0,0 +1,1 @@+{"results":[{"from_user_id_str":"178045354","profile_image_url":"http://a0.twimg.com/profile_images/1214479381/154722_10100141954893059_808622_55276626_7445050_n_normal.jpg","created_at":"Fri, 25 Feb 2011 17:40:04 +0000","from_user":"ChuriSta_gt","id_str":"41190705623732224","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3072\u3089\u304c\u306a\u306e\u300c\u3064\u300d\u306f\u3082\u3046Twitter\u306e\u300c\u3064\u300d\u3060\uff01","id":41190705623732224,"from_user_id":178045354,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twipple.jp/&quot; rel=&quot;nofollow&quot;&gt;\u3064\u3044\u3063\u3077\u308b for iPhone&lt;/a&gt;"},{"from_user_id_str":"71746558","profile_image_url":"http://a2.twimg.com/profile_images/591641547/wwd_normal.gif","created_at":"Fri, 25 Feb 2011 17:40:02 +0000","from_user":"fuckkilldiestar","id_str":"41190699571494912","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u306b\u3057\u3066\u3082Twitter\u3084pixiv\u96e2\u308c\u3092\u3069\u3046\u306b\u304b\u3057\u308d\u674f\u4ec1\u30d6\u30eb\u30de","id":41190699571494912,"from_user_id":71746558,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"131230176","profile_image_url":"http://a2.twimg.com/sticky/default_profile_images/default_profile_5_normal.png","created_at":"Fri, 25 Feb 2011 17:39:58 +0000","from_user":"proory_bot","id_str":"41190683922546688","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u30ea\u30cd\u30f3\u30dd\u30fc\u30c1 http://bit.ly/btjqlH","id":41190683922546688,"from_user_id":131230176,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://proory.com/&quot; rel=&quot;nofollow&quot;&gt;proory tems&lt;/a&gt;"},{"from_user_id_str":"120359038","profile_image_url":"http://a1.twimg.com/profile_images/946521484/200805161826000_normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:58 +0000","from_user":"tukinosuke","id_str":"41190680835391488","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @fuefukioyasumi: \u3082\u3046\u3044\u3044\u3002\u300c\u671d\u751f\u300d\u306a\u3093\u304b\u3044\u3044\u3002BBC\u3082CNN\u3082\u82f1\u8a9e\u653e\u9001\u3060\u3002\u307f\u3093\u306a\u3001\u30c6\u30ec\u30d3\u3092\u3076\u3061\u3063\u3068\u5207\u3063\u3066Twitter\u3092\u898b\u3088\u3046\u3002\u3053\u3053\u306b\u300c\u751f\u304d\u305f\u60c5\u5831\u300d\u304c\u6d41\u308c\u3066\u3044\u308b\u3002\u300c\u751f\u304d\u305f\u53eb\u3073\u300d\u304c\u3042\u308b\u3002\u3053\u3053\u304b\u3089\u3067\u3082\u6b74\u53f2\u306e\u8ee2\u63db\u70b9\u3092\u611f\u3058\u308b\u4e8b\u304c\u3067\u304d\u308b\u306e\u3060\u3002 @gjmorley #libjp","id":41190680835391488,"from_user_id":120359038,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"166512109","profile_image_url":"http://a3.twimg.com/profile_images/1224583217/image_normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:50 +0000","from_user":"yurii1129","id_str":"41190649839620096","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u4eca\u6c17\u4ed8\u3044\u305f\u2026\u307f\u3043\u304f\u3093\u306eTwitter\u540d\u3001\u3084\u307e\u306d\u3084\u3093(\uffe3\u25c7\uffe3;)","id":41190649839620096,"from_user_id":166512109,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"228999619","profile_image_url":"http://a3.twimg.com/sticky/default_profile_images/default_profile_6_normal.png","created_at":"Fri, 25 Feb 2011 17:39:49 +0000","from_user":"takami926","id_str":"41190644793745408","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u7530\u539f\u3055\u3093\u3001\u5fdc\u63f4\u3057\u3066\u307e\u3059\u3002WEB\u3082TWITTER\u3082\u62dd\u898b\u3057\u3066\u307e\u3059\u3002\u7530\u539f\u3055\u3093\u304c\u653f\u6cbb\u5bb6\u306b\u306a\u3063\u3066\u3001\u65e5\u672c\u3092\u5909\u3048\u3066\u304f\u3060\u3055\u3044\u3002\u5c16\u95a3\u3001\u5317\u65b9\u554f\u984c\u5171\u306b\u65e5\u672c\u306e\u653f\u6cbb\u306e\u5931\u6557\u3067\u3059\u3002\u8a55\u8ad6\u5bb6\u306f\u8272\u3005\u8a00\u3063\u3066\u307e\u3059\u304c\u3001\u4e2d\u6771\u307b\u3069\u56fd\u6c11\u306e\u71b1\u6c17\u304c\u7121\u304f\u3001\u683c\u5dee\u304c\u3042\u308a\u3001\u4f55\u3082\u5909\u308f\u3089\u306a\u3044\u666f\u6c17\u4f4e\u8ff7\u306e\u65e5\u672c\u306b\u56fd\u6c11\u306f\u8ae6\u3081\u3066\u308b\u3093\u3067\u3059\u3002","id":41190644793745408,"from_user_id":228999619,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"151773701","profile_image_url":"http://a0.twimg.com/profile_images/1213699753/obi_nao_normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:44 +0000","from_user":"obi_nao","id_str":"41190622949941248","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3060\u3044\u3076\u5e74\u4e0b\u306e\u5f8c\u8f29\u306b\u3001\u30d3\u30b8\u30cd\u30b9\u3084\u308b\u306a\u3089\u771f\u5263\u306bTwitter\u3084\u308c\uff01\u3063\u3066\u6012\u3089\u308c\u305f\u3002\u306a\u306e\u3067\u4eca\u65e5\u304b\u3089\u771f\u5263\u306b\u3064\u3076\u3084\u304f\u3053\u3068\u306b\u3057\u307e\u3059\u3002\u3088\u308d\u3057\u304f\u3067\u3059\u3002","id":41190622949941248,"from_user_id":151773701,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.flight.co.jp/iPhone/TweetMe/&quot; rel=&quot;nofollow&quot;&gt;TweetMe for iPhone&lt;/a&gt;"},{"from_user_id_str":"20817229","profile_image_url":"http://a0.twimg.com/profile_images/1212012721/205_normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:43 +0000","from_user":"allte","id_str":"41190619804082176","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u7206\u7b11\u3057\u3066\u547c\u5438\u56f0\u96e3\u3067\u75d9\u6523\u3059\u308b\u5618\u304f\u3093\u304c\u898b\u308c\u308b\u306e\u306fTwitter\u3060\u3051","id":41190619804082176,"from_user_id":20817229,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.movatwi.jp&quot; rel=&quot;nofollow&quot;&gt;www.movatwi.jp&lt;/a&gt;"},{"from_user_id_str":"166907967","profile_image_url":"http://a2.twimg.com/profile_images/1250526230/___normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:43 +0000","from_user":"NTTYAHOOOOOOOI","id_str":"41190618235412480","metadata":{"result_type":"recent"},"to_user_id":106469594,"text":"@takahirororo \u3066\u304b\u4f55\u3067twitter\u4e0a\u3067\u30a2\u30c9\u30d0\u30a4\u30b9\u3082\u3089\u3063\u3066\u3093\u306d\u3093\uff57","id":41190618235412480,"from_user_id":166907967,"to_user":"takahirororo","geo":null,"iso_language_code":"ja","to_user_id_str":"106469594","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"82581287","profile_image_url":"http://a0.twimg.com/profile_images/774721566/miyaru2_normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:38 +0000","from_user":"kine_rahchaos","id_str":"41190598132244480","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u30a2\u30aa\u30b7\u30de\u3055\u3093\u304cTwitter\u59cb\u3081\u305f\u3060\u3068\u2026","id":41190598132244480,"from_user_id":82581287,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://sourceforge.jp/projects/tween/wiki/FrontPage&quot; rel=&quot;nofollow&quot;&gt;Tween&lt;/a&gt;"},{"from_user_id_str":"136633905","profile_image_url":"http://a2.twimg.com/profile_images/1247161991/yama_normal.png","created_at":"Fri, 25 Feb 2011 17:39:35 +0000","from_user":"yamachi39","id_str":"41190584706285568","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3084\u3063\u3071\u308amixi\u3088\u308atwitter\u306e\u304c\u597d\u304d\u304b\u3082","id":41190584706285568,"from_user_id":136633905,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://jigtwi.jp/?p=1&quot; rel=&quot;nofollow&quot;&gt;jigtwi&lt;/a&gt;"},{"from_user_id_str":"44029295","profile_image_url":"http://a2.twimg.com/profile_images/1112965522/image_normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:32 +0000","from_user":"no2yosee","id_str":"41190572769280000","metadata":{"result_type":"recent"},"to_user_id":97721124,"text":"@hn0345 \u3042\u3001\u500b\u4eba\u7684\u306b\u306f\u4eca\u306ftwitter\u306e\u30d7\u30ed\u30d5\u30a3\u30fc\u30eb\u306b\u66f8\u3044\u3066\u3042\u308b\u30d6\u30ed\u30b0\u306e\u30e6\u30cb\u30c3\u30c8\u3092\u306e\u3093\u3073\u308a\u3084\u3063\u3066\u307e\u3059w","id":41190572769280000,"from_user_id":44029295,"to_user":"hn0345","geo":null,"iso_language_code":"ja","to_user_id_str":"97721124","source":"&lt;a href=&quot;http://www.flight.co.jp/iPhone/TweetMe/&quot; rel=&quot;nofollow&quot;&gt;TweetMe for iPhone&lt;/a&gt;"},{"from_user_id_str":"149872179","profile_image_url":"http://a0.twimg.com/profile_images/1145770150/Winter11_normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:31 +0000","from_user":"xmyyyxx","id_str":"41190566951653376","metadata":{"result_type":"recent"},"to_user_id":95069405,"text":"@rindofu \u653b\u7565\u672c\u3042\u308c\u3070\u3044\u3044\u306e\u306b\u3063\u3066\u601d\u3044\u307e\u3059(T\u03c9T)/~~~ \u79c1\u3082\u76f4\u3057\u305f\u3044\u3068\u3053\u3044\u3063\u3071\u3044\u3060\u3051\u3069\u3001\u3042\u306860\u5e74\u304f\u3089\u3044\u3044\u304d\u3089\u308c\u308b\u306f\u305a\u3060\u3057\u3001\u306e\u3093\u3073\u308a\u76f4\u308c\u3070\u3044\u3044\u306a\u3042 \u304a\u3084\u3059\u307f\u3067\u3059^^\uff01\u308a\u3093\u3055\u3093\u3068twitter\u3067\u4ef2\u826f\u304f\u306a\u308c\u3066\u3088\u304b\u3063\u305f\u3067\u3059\u3063","id":41190566951653376,"from_user_id":149872179,"to_user":"rindofu","geo":null,"iso_language_code":"ja","to_user_id_str":"95069405","source":"&lt;a href=&quot;http://twtr.jp&quot; rel=&quot;nofollow&quot;&gt;Keitai Web&lt;/a&gt;"},{"from_user_id_str":"105579892","profile_image_url":"http://a0.twimg.com/profile_images/1212950455/love_happy_pink_normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:30 +0000","from_user":"love_happy_pink","id_str":"41190566494601216","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u305d\u308c\u898b\u305f\u3044\u306a\u266aRT @kazukt \u3046\u308b\u304a\u307c\u7d75\u3001\u4ed6\u306e\u4eba\u306e\u3082\u898b\u3066\u3044\u305f\u3089\u3084\u305f\u3089\u30de\u30f3\u30ac\u306e\u30ad\u30e3\u30e9\u30af\u30bf\u30fc\u3067\u3080\u3061\u3083\u304f\u3061\u3083\u4e0a\u624b\u3044\u4eba\u304c\uff01\u2026\u3068\u3001\u30d7\u30ed\u30d5\u30a3\u30fc\u30eb\u898b\u305f\u3089\u3001\u306a\u3093\u3068\u3054\u672c\u4eba\u304c\u66f8\u3044\u3066\u3044\u3089\u3063\u3057\u3083\u3063\u305f\u3002Twitter\u3063\u3066\u30b9\u30b4\u30a4\u308f\u3002","id":41190566494601216,"from_user_id":105579892,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.nibirutech.com/&quot; rel=&quot;nofollow&quot;&gt;TwitBird iPad&lt;/a&gt;"},{"from_user_id_str":"16996368","profile_image_url":"http://a0.twimg.com/profile_images/1198892387/pochi_normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:30 +0000","from_user":"HyoYoshikawa","id_str":"41190564187611136","metadata":{"result_type":"recent"},"to_user_id":1800565,"text":"@shinichiro_beck \u73fe\u5730\u304b\u3089\u306a\u3089\u826f\u304f\u3066\u4ed6\u306e\u5730\u57df\u304b\u3089\u3060\u4f59\u8a08\u306a\u3089\u3001\u4f55\u306e\u305f\u3081\u306bTwitter\u304c\u3042\u308b\u306e\u304b\u308f\u304b\u3089\u306a\u3044\u3088\u3002\u3044\u307e\u8d77\u304d\u3066\u308b\u3053\u3068\u306e\u8aac\u660e\u3082\u3064\u304b\u306a\u3044\u3002\u30a2\u30eb\u30b8\u30e3\u30b8\u30fc\u30e9\u304c\u4f1d\u3048\u3066\u308b\u3053\u3068\u304c\u5168\u3066\u3058\u3083\u306a\u3044\u3057\u3001\u65e5\u672c\u306eTV\u306f\u4f1d\u3048\u306a\u3044\u3067\u3057\u3087\u3046\u3002\u77e5\u308a\u305f\u304c\u3063\u3066\u308b\u4eba\u3082\u305f\u304f\u3055\u3093\u3044\u308b\u3002","id":41190564187611136,"from_user_id":16996368,"to_user":"shinichiro_beck","geo":null,"iso_language_code":"ja","to_user_id_str":"1800565","source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;"},{"from_user_id_str":"119070802","profile_image_url":"http://a2.twimg.com/profile_images/1229773005/346d8435099798113a1326e1ba4949ee_normal.jpeg","created_at":"Fri, 25 Feb 2011 17:39:25 +0000","from_user":"takotako726","id_str":"41190545439207424","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u9b54\u6cd5\u5c11\u5973\u307e\u3069\u304b\u30de\u30ae\u30ab\u306e\u53cd\u97ff\u304ctwitter\u3067\u306f\u3093\u3071\u306d\u3048\u4ef6\u3002","id":41190545439207424,"from_user_id":119070802,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://z.twipple.jp/&quot; rel=&quot;nofollow&quot;&gt;\u3064\u3044\u3063\u3077\u308b/twipple&lt;/a&gt;"},{"from_user_id_str":"165695316","profile_image_url":"http://a1.twimg.com/profile_images/681748782/ring_normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:21 +0000","from_user":"pandora_shotbar","id_str":"41190526447390720","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u300e\u30b7\u30e7\u30c3\u30c8\u30d0\u30fc\u3000\u30d1\u30f3\u30c9\u30e9\u3000\u30c4\u30a4\u30c3\u30bf\u30fc\u306e\u3064\u3076\u3084\u304d\u300f\u30b7\u30e7\u30c3\u30c8\u30d0\u30fc\u3000\u30d1\u30f3\u30c9\u30e9\u3000\u30c4\u30a4\u30c3\u30bf\u30fc\u306e\u3064\u2026\uff5chttp://pandora-twitter.seesaa.net/article/187809936.html","id":41190526447390720,"from_user_id":165695316,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://blog.seesaa.jp/&quot; rel=&quot;nofollow&quot;&gt;SeesaaBlog&lt;/a&gt;"},{"from_user_id_str":"88862311","profile_image_url":"http://a2.twimg.com/profile_images/1182243698/161113_100001897885617_169366_q_normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:16 +0000","from_user":"amebaguide","id_str":"41190504926416896","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u300e\u30a2\u30e1\u30d6\u30ed\u653b\u7565\u30ac\u30a4\u30c9\u306e\u30c4\u30a4\u30c3\u30bf\u30fc\u306e\u3064\u3076\u3084\u304d\u300f\u30d6\u30ed\u30b0\u306e\u653b\u7565\u3000\u30c4\u30a4\u30c3\u30bf\u30fc\u306e\u3064\u3076\u3084\u304d\u96c6\uff5chttp://accessup-twitter.seesaa.net/article/187809931.html","id":41190504926416896,"from_user_id":88862311,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://blog.seesaa.jp/&quot; rel=&quot;nofollow&quot;&gt;SeesaaBlog&lt;/a&gt;"},{"from_user_id_str":"186895007","profile_image_url":"http://a2.twimg.com/profile_images/1229610799/image_normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:11 +0000","from_user":"zizikt","id_str":"41190486123356161","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3053\u308c\u306f\u500b\u4eba\u7684\u306b\u306f\u91cd\u5b9d\u3067\u3059\u3002\n\u25c6iPhone&amp;iPad\u5411\u3051Twitter\u30a2\u30d7\u30ea\u300c\u3064\u3044\u3063\u3077\u308b\u300d\u306bEvernote\u3068\u306e\u9023\u643a\u6a5f\u80fd\u3092\u8ffd\u52a0 \nhttp://bit.ly/hPf44u","id":41190486123356161,"from_user_id":186895007,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"132320738","profile_image_url":"http://a3.twimg.com/profile_images/1229760475/image_normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:04 +0000","from_user":"faina_","id_str":"41190453592334336","metadata":{"result_type":"recent"},"to_user_id":167894537,"text":"@gjmorley \u30e2\u30fc\u30ea\u30fc\u3055\u3093\u306e\u7ffb\u8a33\u306e\u8a00\u8449\u306e\u30bb\u30f3\u30b9\u304c\u3059\u304d\u3067\u3059\u3002\u305d\u3057\u3066\u3001Twitter\u3067\u77e5\u308b\u3001\u73fe\u5730\u306e\u4eba\u306e\u76ee\u306e\u529b\u3084\u3001\u60c5\u5831\u3092\u4f1d\u3048\u3066\u304f\u308c\u308b\u7686\u3055\u3093\u306e\u60c5\u71b1\u306b\u611f\u52d5\u3057\u3066\u3044\u307e\u3059\u3002\u611f\u52d5\u3059\u308b\u3063\u3066\u3059\u3054\u3044\u30a8\u30cd\u30eb\u30ae\u30fc\u3067\u3059\uff01\uff01","id":41190453592334336,"from_user_id":132320738,"to_user":"gjmorley","geo":null,"iso_language_code":"ja","to_user_id_str":"167894537","source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"24870552","profile_image_url":"http://a3.twimg.com/profile_images/907130634/4_normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:03 +0000","from_user":"tana1192","id_str":"41190450480021504","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u500b\u4eba\u7684\u306b\u306f\u3001\u30bf\u30b0\u5f3e\u304d\u3059\u308c\u3070\u3044\u3044\uff08\u5f3e\u3051\u308b\u30c4\u30fc\u30eb\u3092\u4f7f\u3063\u3066Twitter\u3092\u3059\u308c\u3070\u3044\u3044\uff09\u3068\u601d\u3046\u3093\u3060\u3051\u3069\u306a\u3042\u3002\u307f\u3093\u306a\u305d\u308c\u306a\u308a\u306bTwitter\u3084\u308b\u4eba\u3060\u3057\u3001\u30c4\u30fc\u30eb\u9078\u3073\u304f\u3089\u3044\u5e45\u3092\u6301\u3063\u3066\u3084\u3063\u3066\u3082\u3002 \u307e\u3042\u3001\u4ffa\u30a2\u30cb\u30e1\u306f\u89b3\u308b\u89b3\u308b\u8a50\u6b3a\u3059\u308b\u3060\u3051\u306e\u4eba\u3060\u304b\u3089\u95a2\u4fc2\u306a\u3044\u3093\u3060\u3051\u3069\u3082\uff57\uff57\u5b9f\u6cc1\u306f\u697d\u3057\u305d\u3046\u306b\u898b\u3048\u308b\u3088\u3002","id":41190450480021504,"from_user_id":24870552,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://sourceforge.jp/projects/tween/wiki/FrontPage&quot; rel=&quot;nofollow&quot;&gt;Tween&lt;/a&gt;"},{"from_user_id_str":"58509646","profile_image_url":"http://a3.twimg.com/sticky/default_profile_images/default_profile_2_normal.png","created_at":"Fri, 25 Feb 2011 17:39:02 +0000","from_user":"kakuyas","id_str":"41190446663344128","metadata":{"result_type":"recent"},"to_user_id":107076877,"text":"@Dayspool \u6df1\u591c\uff12\uff19\u6642\u304f\u3089\u3044\u307e\u3067\u306f\u6bce\u65e5\u55b6\u696d\u3057\u3066\u308b\u306e\u3067\u3001twitter\u3001\u30e1\u30c3\u30bb\u3001PS3\u306a\u3069\u3067\u3088\u3093\u3067\u3051\u308c\u3002\u3053\u3063\u3061\u304b\u3089\u58f0\u304b\u3051\u308b\u3053\u3068\u3082\u3042\u308b\u304b\u3082\u3060\u304c\uff57","id":41190446663344128,"from_user_id":58509646,"to_user":"Dayspool","geo":null,"iso_language_code":"ja","to_user_id_str":"107076877","source":"&lt;a href=&quot;http://cheebow.info/chemt/archives/2007/04/twitterwindowst.html&quot; rel=&quot;nofollow&quot;&gt;Twit for Windows&lt;/a&gt;"},{"from_user_id_str":"149128527","profile_image_url":"http://a2.twimg.com/profile_images/1254922702/image_normal.jpg","created_at":"Fri, 25 Feb 2011 17:39:00 +0000","from_user":"uni13yoki","id_str":"41190437087748096","metadata":{"result_type":"recent"},"to_user_id":119468594,"text":"@kuro_wr84 \u304a\u308c\u3068\u304a\u524d\u3067Twitter\u3067\u4f1a\u8a71\u3057\u305f\u3089\u3001\u5927\u5909\u306a\u3053\u3068\u306b\u306a\u308b\u306a","id":41190437087748096,"from_user_id":149128527,"to_user":"kuro_wr84","geo":null,"iso_language_code":"ja","to_user_id_str":"119468594","source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"18670595","profile_image_url":"http://a2.twimg.com/profile_images/1250674062/ProfilePhoto_normal.png","created_at":"Fri, 25 Feb 2011 17:38:59 +0000","from_user":"nyuuuuun","id_str":"41190436450222080","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3078\u30fc\u3053\u3093\u306a\u306b\u3088\u304f\u4f3c\u308b\u3053\u3068\u3082\u3042\u308b\u3093\u3060\u30fc\nhttp://twitter.com/Re_44/status/35943233016168448\nhttp://twitter.com/3510_misaka/status/36122114821988352","id":41190436450222080,"from_user_id":18670595,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"96267603","profile_image_url":"http://a3.twimg.com/profile_images/1245253473/DSiLL3_normal.jpg","created_at":"Fri, 25 Feb 2011 17:38:59 +0000","from_user":"arivis","id_str":"41190433119940608","metadata":{"result_type":"recent"},"to_user_id":null,"text":"http://twitter.com/#!/arivis/status/40636755380142080 \u306a\u3093\u3064\u3046\u304b\u3053\u308c\u3067\u8d64\u3063\u3066\u306e\u3082\u3061\u3087\u3063\u3068\u3042\u308c\u3063\u3059\u306d\u3002\u305d\u3057\u3066\u4e88\u671f\u3057\u3066\u3044\u305f\u304b\u306e\u3088\u3046\u3060","id":41190433119940608,"from_user_id":96267603,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"148097776","profile_image_url":"http://a3.twimg.com/profile_images/1166904064/broken-heart_normal.png","created_at":"Fri, 25 Feb 2011 17:38:56 +0000","from_user":"ReajuBreaker_A","id_str":"41190422135058432","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u672c\u57a2\u30d5\u30a9\u30ed\u30fc\u3088\u308d\u3057\u304f\u3067\u3059\u2192 http://twitter.com/ReajuBreaker \uff08\uff20\u3060\u3068\u30ea\u30d7\u30e9\u30a4\u304c\u57cb\u307e\u308b\u305f\u3081\u3001URL\u306b\u3057\u3066\u3042\u308a\u307e\u3059\uff09","id":41190422135058432,"from_user_id":148097776,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.twitter.com/ReajuBreaker&quot; rel=&quot;nofollow&quot;&gt;\u73fe\u5145\u6bba\u3057\u306e\u7121\u4eba\u9023\u545f\uff1c\u30aa\u30fc\u30c8\u30c4\u30a4\u30fc\u30c8\uff1e&lt;/a&gt;"},{"from_user_id_str":"59711654","profile_image_url":"http://a2.twimg.com/profile_images/416212928/flowers-06-1_normal.jpg","created_at":"Fri, 25 Feb 2011 17:38:56 +0000","from_user":"agamo45","id_str":"41190421522546688","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @ebizo66: \u30ea\u30d3\u30a2\u30fb\u30c8\u30ea\u30dd\u30ea\u306eTw\uff1a\u6551\u6025\u8eca\u304c\u6765\u305f\u304c\u3001\u8ca0\u50b7\u8005\u3092\u8eca\u5185\u3067\u6bba\u5bb3\u3057\u3066\u3044\u305f\u3001\u3068\u3002\uff08\u6ec5\u8336\u82e6\u8336\u3060\uff01\uff09http://ow.ly/43pQ2 #libjp","id":41190421522546688,"from_user_id":59711654,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;"},{"from_user_id_str":"145311373","profile_image_url":"http://a3.twimg.com/profile_images/1187802576/a_gam_02_normal.png","created_at":"Fri, 25 Feb 2011 17:38:55 +0000","from_user":"nogic1008","id_str":"41190418141937664","metadata":{"result_type":"recent"},"to_user_id":88117317,"text":"@runasoru \u307e\u3042\u5acc\u306a\u3089\u6df1\u591c\u5e2f\u306bTwitter\u898b\u306a\u304d\u3083\u3044\u3044\u3060\u3051\u3067\u3059\u304b\u3089\u306d\u30fc \u30103/19BDM\u30aa\u30d5http://twvt.us/bdoff_kansai\u3011","id":41190418141937664,"from_user_id":145311373,"to_user":"runasoru","geo":null,"iso_language_code":"ja","to_user_id_str":"88117317","source":"&lt;a href=&quot;http://sourceforge.jp/projects/tween/wiki/FrontPage&quot; rel=&quot;nofollow&quot;&gt;Tween&lt;/a&gt;"},{"from_user_id_str":"195851300","profile_image_url":"http://a2.twimg.com/profile_images/1124572815/be7b4c0a06cb60e8_normal.jpg","created_at":"Fri, 25 Feb 2011 17:38:49 +0000","from_user":"LoveLAscrewDoll","id_str":"41190391147540480","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @jp_mjp: SCREW TV SHOW Vol.6\u306e\u653e\u9001\u304c\u7121\u4e8b\u306b\u7d42\u4e86\u3044\u305f\u3057\u307e\u3057\u305f\u3002\u3054\u89a7\u9802\u304d\u3042\u308a\u304c\u3068\u3046\u3054\u3056\u3044\u307e\u3057\u305f\uff01\u6b21\u56de\u653e\u9001\u306f3\u670818\u65e5(\u91d1)\u3067\u3059\u3002\u8996\u8074\u8005\u30d7\u30ec\u30bc\u30f3\u30c8\u306e\u8a73\u7d30\u306f\u5f8c\u65e5MJP\u3067\u304a\u77e5\u3089\u305b\u3044\u305f\u3057\u307e\u3059\u3002\u305d\u306e\u969b\u306b\u306f\u3001\u3053\u306eTwitter\u3067\u3082\u304a\u77e5\u3089\u305b\u3044\u305f\u3057\u307e\u3059\u306e\u3067\u3001\u304a\u697d\u3057\u307f\u306b\uff01#SCREWTV","id":41190391147540480,"from_user_id":195851300,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://z.twipple.jp/&quot; rel=&quot;nofollow&quot;&gt;\u3064\u3044\u3063\u3077\u308b/twipple&lt;/a&gt;"},{"from_user_id_str":"178045354","profile_image_url":"http://a0.twimg.com/profile_images/1214479381/154722_10100141954893059_808622_55276626_7445050_n_normal.jpg","created_at":"Fri, 25 Feb 2011 17:38:48 +0000","from_user":"ChuriSta_gt","id_str":"41190389549510656","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3053\u3053\u4e00\u9031\u9593Twitter\u304c\u3084\u305f\u3089\u697d\u3057\u3044\u306e\u3067\u3059\u3051\u3069\u30fc\u30fc\u30fc\uff01\u306b\u3072\u3072\u3063\u7b11","id":41190389549510656,"from_user_id":178045354,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twipple.jp/&quot; rel=&quot;nofollow&quot;&gt;\u3064\u3044\u3063\u3077\u308b for iPhone&lt;/a&gt;"},{"from_user_id_str":"50592111","profile_image_url":"http://a0.twimg.com/profile_images/1252176582/lFjpi_normal.jpg","created_at":"Fri, 25 Feb 2011 17:38:48 +0000","from_user":"crowNeko","id_str":"41190387271864320","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u30a8\u30ed\u30b2\u30d7\u30ec\u30a4\u3057\u306a\u304c\u3089\u307e\u3069\u30de\u30aeTL\u3092\u898b\u306a\u3044\u3088\u3046\u306b\u534a\u76ee\u306b\u306a\u308a\u306a\u304c\u3089Twitter\u3057\u3066\u308b\u6df1\u591c2\u6642\u534a\u3002","id":41190387271864320,"from_user_id":50592111,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://stone.com/Twittelator&quot; rel=&quot;nofollow&quot;&gt;Twittelator&lt;/a&gt;"},{"from_user_id_str":"7141467","profile_image_url":"http://a3.twimg.com/profile_images/1198540741/icon12912579345276_normal.jpg","created_at":"Fri, 25 Feb 2011 17:38:40 +0000","from_user":"hina_geshi","id_str":"41190355961380864","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u7d50\u5c40\u30cd\u30bf\u30d0\u30ec\u3059\u3093\u306a\u3063\u3066\u8a00\u3046\u65b9\u304c\u30a2\u30ec\u3060\u3088\u306d\u3001\u3057\u306a\u3044\u308f\u3051\u7121\u3044\u3058\u3083\u306a\u3044\u8133\u5185\u5782\u308c\u6d41\u3057\u304cTwitter\u306a\u3093\u3060\u304b\u3089\u3055","id":41190355961380864,"from_user_id":7141467,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://projects.playwell.jp/go/Saezuri&quot; rel=&quot;nofollow&quot;&gt;Saezuri&lt;/a&gt;"},{"from_user_id_str":"149608984","profile_image_url":"http://a0.twimg.com/profile_images/1205950885/090702_normal.jpg","created_at":"Fri, 25 Feb 2011 17:38:39 +0000","from_user":"Mr_Tsubaki","id_str":"41190349447774208","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u307e\u3069\u304b\u306fTwitter\u5408\u308f\u305b\u3066\uff11\u6642\u9593\u306f\u697d\u3057\u3044","id":41190349447774208,"from_user_id":149608984,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://z.twipple.jp/&quot; rel=&quot;nofollow&quot;&gt;\u3064\u3044\u3063\u3077\u308b/twipple&lt;/a&gt;"},{"from_user_id_str":"122271550","profile_image_url":"http://a0.twimg.com/profile_images/961936499/neoneko_bot5jpg_____normal.jpg","created_at":"Fri, 25 Feb 2011 17:38:39 +0000","from_user":"neoneko_bot5","id_str":"41190348726345728","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3010\u30c6\u30b9\u30c8\uff1a23\u3011\u4eca\u65e5\u304b\u3089\u5ba3\u4f1d\u958b\u59cb\u2606\u306d\u304a\u306d\u3053\u306eTwitter \u3067 EasyBottex\u3000\u3067\u304d\u308b\u307e\u3067\uff01\u2192http://neoneko.blog31.fc2.com/","id":41190348726345728,"from_user_id":122271550,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www20.atpages.jp/neoneko/&quot; rel=&quot;nofollow&quot;&gt;neoneko_bot5&lt;/a&gt;"},{"from_user_id_str":"127069188","profile_image_url":"http://a3.twimg.com/sticky/default_profile_images/default_profile_6_normal.png","created_at":"Fri, 25 Feb 2011 17:38:37 +0000","from_user":"MYCHEBOT","id_str":"41190340908163074","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u30eb\u30d5\u3055\u3093\u304c\u914d\u4fe1\u3092\u7d42\u4e86\u3057\u307e\u3057\u305f\uff01/ http://777labo.com/mychecker/view/745.php / \u30c8\u30d4\u30c3\u30af:XSplit\u30c6\u30b9\u30c8\u3000twitter\u2192http://twitter.com/rukh01","id":41190340908163074,"from_user_id":127069188,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://777labo.com/mychecker/&quot; rel=&quot;nofollow&quot;&gt;MYCHEBOT&lt;/a&gt;"},{"from_user_id_str":"107626142","profile_image_url":"http://a1.twimg.com/profile_images/1104968421/SuperMaika1_normal.jpg","created_at":"Fri, 25 Feb 2011 17:38:34 +0000","from_user":"MaikaPaPa","id_str":"41190328614666240","metadata":{"result_type":"recent"},"to_user_id":null,"text":"75\u5e74\u524d\u306e\u4eca\u65e52\u670826\u65e5\u672a\u660e\u3001\u82e5\u304d\u9752\u5e74\u5c06\u6821\u3089\u304c\u653f\u6cbb\u8150\u6557\u3068\u8fb2\u6751\u306e\u56f0\u7aae\u306b\u5bfe\u3057\u3066\u6c7a\u8d77\u3057\u307e\u3057\u305f\u3002\u3082\u3061\u308d\u3093\u5f53\u6642\u306fTwitter\u3082FB\u3082\u3001\u307e\u305f\u5f53\u7136\u306a\u304c\u3089Internet\u3082\u3042\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u4e00\u65b9\u3001\u30c1\u30e5\u30cb\u30b8\u30a2\u3001\u30a8\u30b8\u30d7\u30c8\u3001\u4e2d\u6771\u306b\u5e83\u304c\u308b\u53cd\u653f\u5e9c\u30c7\u30e2\u3068\u5f37\u6a29\u4f53\u5236\u306e\u5d29\u58ca\u3002\u73fe\u5728\u306e\u65e5\u672c\u3067\u306f\u8003\u3048\u3089\u308c\u306a\u3044\u3053\u3068\u3067\u3059\u3002","id":41190328614666240,"from_user_id":107626142,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"11111695","profile_image_url":"http://a1.twimg.com/sticky/default_profile_images/default_profile_0_normal.png","created_at":"Fri, 25 Feb 2011 17:38:28 +0000","from_user":"kumatchipooh","id_str":"41190306145775617","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @inosenaoki: \u306a\u308b\u307b\u3069\u306d\u3002 RT @dansrmz @inosenaoki \u3053\u3061\u3089\u304c\u30bd\u30d5\u30c8\u30d0\u30f3\u30af\u526f\u793e\u9577\u306e\u677e\u672c\u5fb9\u4e09\u3055\u3093\u306e\u3001Twitter\u306b\u95a2\u3059\u308b\u8ad6\u8003\u3067\u3059\u3002 \u25b6 &quot;Twitter\u306e2\u30c1\u30e3\u30f3\u30cd\u30eb\u5316\u306f\u9632\u6b62\u51fa\u6765\u308b\u304b&quot; http://t.co/Mwzb8Zf","id":41190306145775617,"from_user_id":11111695,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"212161909","profile_image_url":"http://a1.twimg.com/profile_images/1252895790/natm01_normal.gif","created_at":"Fri, 25 Feb 2011 17:38:28 +0000","from_user":"n34hitman","id_str":"41190303830511616","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Twitter\u30fb\u30d6\u30ed\u30b0\u30a2\u30d5\u30a3\u30ea\u30a8\u30a4\u30c8\u81ea\u52d5\u6295.... http://goo.gl/a4RHB 4054","id":41190303830511616,"from_user_id":212161909,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twibow.net/&quot; rel=&quot;nofollow&quot;&gt;Twibow&lt;/a&gt;"},{"from_user_id_str":"105945593","profile_image_url":"http://a2.twimg.com/sticky/default_profile_images/default_profile_5_normal.png","created_at":"Fri, 25 Feb 2011 17:38:24 +0000","from_user":"nakano_bot","id_str":"41190285710983168","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3053\u306ebot\u306f\u4e0d\u52d5\u7523\u4f1a\u793e\u69d8\u5411\u3051\u306e\u4e0d\u52d5\u7523\u30dd\u30fc\u30bf\u30eb\u30b5\u30a4\u30c8\u3078\u306e\u4e00\u62ec\u8ee2\u9001\uff0bTwitter\u7121\u6599\u8ee2\u9001\u30b5\u30fc\u30d3\u30b9\u306b\u3088\u308a\u3064\u3076\u3084\u304d\u307e\u3059\u3002\u3054\u5229\u7528\u306b\u306a\u308a\u305f\u3044\u5834\u5408\u306f http://bit.ly/demey0 \u306b\u304a\u6c17\u8efd\u306b\u304a\u554f\u3044\u5408\u308f\u305b\u4e0b\u3055\u3044\u3002","id":41190285710983168,"from_user_id":105945593,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.teramax.jp/aboutus.html&quot; rel=&quot;nofollow&quot;&gt;tmxbot&lt;/a&gt;"},{"from_user_id_str":"170904226","profile_image_url":"http://a2.twimg.com/profile_images/1249020397/1789kb_normal.jpg","created_at":"Fri, 25 Feb 2011 17:38:21 +0000","from_user":"1789kb","id_str":"41190274428305408","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3068\u308a\u3042\u3048\u305a\u4eca\u56de\u521d\u3081\u3066\u30ea\u30a2\u30eb\u30bf\u30a4\u30e0\u3067\u898b\u3066\u308f\u304b\u3063\u305f\u306e\u306f\u3001QB\u306f\u5168\u8eab\u304c\u30a2\u30f3\u30d1\u30f3\u30bf\u30a4\u30d7\u3060\u3068\u3044\u3046\u3053\u3068\u3068\u3001\u307e\u3069\u30de\u30ae\u7d42\u4e86\u5f8c\u306eTwitter\u306e\u3056\u308f\u3064\u304d\u304c\u3059\u3054\u3044\u3068\u3044\u3046\u3053\u3068","id":41190274428305408,"from_user_id":170904226,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.echofon.com/&quot; rel=&quot;nofollow&quot;&gt;Echofon&lt;/a&gt;"},{"from_user_id_str":"166169644","profile_image_url":"http://a1.twimg.com/profile_images/1217017679/image_normal.jpg","created_at":"Fri, 25 Feb 2011 17:38:18 +0000","from_user":"Okkkkkkkkkkun","id_str":"41190262420017152","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3063\u3066Twitter\u3067\u3064\u3076\u3084\u304f\u81ea\u5206\u3082\u76f8\u5f53\u5c0f\u3055\u3044\u306a\u3002","id":41190262420017152,"from_user_id":166169644,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"196623456","profile_image_url":"http://a3.twimg.com/profile_images/1219076661/image_normal.jpg","created_at":"Fri, 25 Feb 2011 17:38:06 +0000","from_user":"NoMoneyClub","id_str":"41190213342601216","metadata":{"result_type":"recent"},"to_user_id":195462431,"text":"@yosal15 \nTwitter\u3084\u3063\u3066\u308b\u3089\u3057\u3044\u3002\u3051\u3069\u3001\u898b\u3064\u304b\u3089\u306a\u3044\u3002","id":41190213342601216,"from_user_id":196623456,"to_user":"yosal15","geo":null,"iso_language_code":"ja","to_user_id_str":"195462431","source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"126984048","profile_image_url":"http://a3.twimg.com/profile_images/1254579571/zipyaru-20090829-23-0012_normal.png","created_at":"Fri, 25 Feb 2011 17:38:02 +0000","from_user":"minaduki_naduki","id_str":"41190196418584576","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u203b\u30e1\u30fc\u30eb\u3067\u5973\u306e\u5b50\u53e3\u8aac\u304f\u306e\u306b\u5fd9\u3057\u3044\u3093\u3060\u305d\u3046\u3067\u3059\u3000\u307e\u3058\u3058\u3054\u308d RT @yowano_k: \u5fd9\u3057\u304f\u3066\u3082Twitter\u306b\u9854\u3092\u51fa\u3057\u305f\u304f\u306a\u308b\u50d5\u30a7\u2026\u2026","id":41190196418584576,"from_user_id":126984048,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://seesmic.com/seesmic_desktop/sd2&quot; rel=&quot;nofollow&quot;&gt;Seesmic Desktop&lt;/a&gt;"},{"from_user_id_str":"200568705","profile_image_url":"http://a3.twimg.com/profile_images/1242179765/101230_1355_01_normal.jpg","created_at":"Fri, 25 Feb 2011 17:38:01 +0000","from_user":"hammerstrap","id_str":"41190190428979200","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6628\u65e5\u91d1\u66dc\u65e5\u306f\u732e\u8840\u306b\u5f80\u304f\u3002\u4eca\u306e\u50d5\u306e\u4eba\u9593\u3068\u3057\u3066\u306e\u4fa1\u5024\u306f\u3001\u7cbe\u3005\u3053\u306e\u7a0b\u5ea6\u3002\u8179\u304c\u6e1b\u3063\u305f\u3002\u5374\u8aac\u3002twitter\u306e\u767e\u56db\u5341\u6587\u5b57\u306b\u82db\u3005\u3068\u3057\u3066\u3001\u4e00\u3064\u306e\u4eee\u8aac\u306b\u4fe1\u6191\u6027\u3092\u5f97\u308b\u3002\u6226\u6642\u4e0b\u306e\u7d19\u306e\u7d71\u5236\u3068\u3001\u4e2d\u5cf6\u6566\u306e\u6587\u7ae0\u306e\u95a2\u4fc2\u6027\u3002\u73fe\u5728\u30a6\u30a7\u30d6\u30ed\u30b0\u306b\u3001\u7e8f\u3081\u3066\u3044\u308b\u6700\u4e2d\u3002\u8fd1\u65e5\u516c\u958b\u4e88\u5b9a\u3002","id":41190190428979200,"from_user_id":200568705,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"164970427","profile_image_url":"http://a0.twimg.com/profile_images/1227176662/9192b021ef85ce9902c28955f86e604b_normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:59 +0000","from_user":"syuigetsuIT","id_str":"41190184041197569","metadata":{"result_type":"recent"},"to_user_id":141926331,"text":"@iliad_tga \u30a4\u30ea\u30a2\u30b9\u3055\u3093\u304c\u305d\u306e\u8fba\u7406\u89e3\u306e\u3042\u308b\u3053\u3068\u306f\u5206\u304b\u3063\u3066\u307e\u3059\uff57\u3000\u30cd\u30bf\u30d0\u30ec\u4e91\u3005\u306ftwitter\u306e\u6c7a\u5b9a\u7684\u306a\u6b20\u70b9\u3060\u3068\u601d\u3044\u307e\u3059\u306d\u3047\u3002","id":41190184041197569,"from_user_id":164970427,"to_user":"iliad_tga","geo":null,"iso_language_code":"ja","to_user_id_str":"141926331","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"94526795","profile_image_url":"http://a0.twimg.com/profile_images/679411954/akb48_in_normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:56 +0000","from_user":"akb48_in","id_str":"41190171248431104","metadata":{"result_type":"recent"},"to_user_id":null,"text":"AKB48\u67cf\u6728\u7531\u7d00 \u304a\u75b2\u308c\u69d8\u3002: \n\u3053\u3093\u3070\u3093\u306f(^O^)\uff0f\u266a\n\u3000\n\u3000\n\u3000\n\u4eca\u65e5\u306e\u516c\u6f14\u697d\u3057\u304b\u3063\u305f\u301c\n\u3000\n\u3000\n\u4e45\u3057\u3076\u308a\u3067\u7dca\u5f35\u3057\u305f\u3051\u3069\u3001\u306f\u3058\u3051\u307e\u304f\u3063\u305f\u305c\u3043\uff01\uff01\n\u3000\n\u3000\n\u3000\nMC\u306f\u565b\u307f\u307e\u304f\u308a\u306e\u30c6\u30f3\u30d1\u308a\u307e\u304f... http://bit.ly/ie8bEa akb48 twitter","id":41190171248431104,"from_user_id":94526795,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"222270374","profile_image_url":"http://a3.twimg.com/profile_images/1250946143/P1040062_normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:53 +0000","from_user":"chrxpac","id_str":"41190158455943168","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3010\u5b9a\u671f\u3011Twitter\u306f\uff8a\uff9f\uff7f\uff7a\uff9d\u304b\u3089\u3057\u304b\u3067\u304d\u306a\u3044\u306e\u3067\uff98\uff8c\uff9f\u8fd4\u3057\u304c\u9045\u304f\u306a\u308a\u307e\u3059(\u00b4\u30fb\u03c9\u30fb\uff40)\u3054\u4e86\u627f\u4e0b\u3055\u3044!!","id":41190158455943168,"from_user_id":222270374,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twittbot.net/&quot; rel=&quot;nofollow&quot;&gt;twittbot.net&lt;/a&gt;"},{"from_user_id_str":"64009258","profile_image_url":"http://a2.twimg.com/profile_images/1154025023/Mixi___normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:52 +0000","from_user":"Tatsuyuko","id_str":"41190153447944192","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u4f01\u696d\u304c\u5c31\u6d3b\u751f\u306bFacebook\u3092\u4f7f\u3063\u3066\u7533\u8acb\u305b\u3088\u3068\u3044\u3044\u3001\u4f01\u696d\u304cFacebook\u3092\u4f7f\u3048\u3068\u4f01\u696d\u304c\u8a00\u3063\u3066\u304d\u305f\u308a\u3059\u308c\u3070\u305d\u308c\u3069\u3053\u308d\u3058\u3083\u306a\u3044\u304b\u3068\u3002\u5c11\u306a\u304f\u3068\u3082\u5c31\u6d3b\u3067Twitter\u306e\u8a00\u52d5\u3084\u30d5\u30a9\u30ed\u30ef\u30fc\u3092\u8abf\u67fb\u3059\u308b\u4f01\u696d\u306f\u304b\u306a\u308a\u591a\u3044\u3067\u3059 RT @Isshee: \u4f01\u696d\u3067\u3082\u540c\u3058\u3067\u3057\u3087 RT","id":41190153447944192,"from_user_id":64009258,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://sourceforge.jp/projects/tween/wiki/FrontPage&quot; rel=&quot;nofollow&quot;&gt;Tween&lt;/a&gt;"},{"from_user_id_str":"105145175","profile_image_url":"http://static.twitter.com/images/default_profile_normal.png","created_at":"Fri, 25 Feb 2011 17:37:49 +0000","from_user":"snao813","id_str":"41190141703753728","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @yumisaiki: \u795e\u69d8\u306f\u672c\u5f53\u306b\u4eba\u9593\u3092\u3059\u3070\u3089\u3057\u3044\u30d0\u30e9\u30f3\u30b9\u3067\u914d\u7f6e\u3057\u3066\u304a\u3089\u308c\u308b\u3002\u795d\u5cf6\u307f\u305f\u3044\u306a\u5c0f\u3055\u3044\u3068\u3053\u308d\u306b\u306a\u3093\u3067\u3053\u3093\u306a\u306b\u7acb\u6d3e\u306a\u4eba\u304c\u305f\u304f\u3055\u3093\u3044\u308b\u3093\u3060\u308d\u3046\u3002\u795e\u69d8\u3042\u308a\u304c\u3068\u3046\u3002\u672c\u5f53\u306b\u3042\u308a\u304c\u3068\u3046\u3002\u4eba\u9593\u3063\u3066\u3059\u3070\u3089\u3057\u3044\u3068\u601d\u3048\u305f\u3002twitter\u3082\u3042\u308a\u304c\u3068\u3046\u3002\u3000#kaminoseki","id":41190141703753728,"from_user_id":105145175,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://seesmic.com/seesmic_desktop/sd2&quot; rel=&quot;nofollow&quot;&gt;Seesmic Desktop&lt;/a&gt;"},{"from_user_id_str":"203667204","profile_image_url":"http://a2.twimg.com/profile_images/1254664000/eve_blackcat_normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:46 +0000","from_user":"eve_blackcat","id_str":"41190129292943361","metadata":{"result_type":"recent"},"to_user_id":146894340,"text":"@kuroiso02 \u3057\u308a\u3068\u308a\u3068\u304b\uff1f\n\u2026\u3067\u3082Twitter\u3060\u3068\u7d42\u308f\u3089\u306a\u304f\u306a\u308b\u30b1\u30c9\u2026","id":41190129292943361,"from_user_id":203667204,"to_user":"kuroiso02","geo":null,"iso_language_code":"ja","to_user_id_str":"146894340","source":"&lt;a href=&quot;http://twipple.jp/&quot; rel=&quot;nofollow&quot;&gt;\u3064\u3044\u3063\u3077\u308b for iPhone&lt;/a&gt;"},{"from_user_id_str":"6528403","profile_image_url":"http://a0.twimg.com/profile_images/1240591244/image_normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:45 +0000","from_user":"sortiee","id_str":"41190125924917248","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u30a2\u30cb\u30e1\u5b9f\u6cc1\u306ftwitter\u306e\u764c","id":41190125924917248,"from_user_id":6528403,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://sites.google.com/site/peraperaprv/Home&quot; rel=&quot;nofollow&quot;&gt;P3:PeraPeraPrv&lt;/a&gt;"},{"from_user_id_str":"186058304","profile_image_url":"http://a0.twimg.com/profile_images/1250454564/IMG_0210_normal.JPG","created_at":"Fri, 25 Feb 2011 17:37:37 +0000","from_user":"00o8o00","id_str":"41190090386448384","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @fuefukioyasumi: \u3082\u3046\u3044\u3044\u3002\u300c\u671d\u751f\u300d\u306a\u3093\u304b\u3044\u3044\u3002BBC\u3082CNN\u3082\u82f1\u8a9e\u653e\u9001\u3060\u3002\u307f\u3093\u306a\u3001\u30c6\u30ec\u30d3\u3092\u3076\u3061\u3063\u3068\u5207\u3063\u3066Twitter\u3092\u898b\u3088\u3046\u3002\u3053\u3053\u306b\u300c\u751f\u304d\u305f\u60c5\u5831\u300d\u304c\u6d41\u308c\u3066\u3044\u308b\u3002\u300c\u751f\u304d\u305f\u53eb\u3073\u300d\u304c\u3042\u308b\u3002\u3053\u3053\u304b\u3089\u3067\u3082\u6b74\u53f2\u306e\u8ee2\u63db\u70b9\u3092\u611f\u3058\u308b\u4e8b\u304c\u3067\u304d\u308b\u306e\u3060\u3002 @gjmorley #libjp","id":41190090386448384,"from_user_id":186058304,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"49219631","profile_image_url":"http://a2.twimg.com/profile_images/1231873117/__2-colornow__2__normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:33 +0000","from_user":"irisgazer","id_str":"41190073227550720","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3067\u3059\u3088\u306d\u3047 RT @minamitaiheiyou: \u6545\u306b\u30cd\u30bf\u30d0\u30ec\u3092\u3059\u308b\u306e\u3082\u81ea\u7531\u3002\u30cd\u30bf\u3070\u308c\u3057\u305f\u4eba\u3092\u30c7\u30a3\u30b9\u308b\u306e\u3082\u81ea\u7531\u3002\u3042\u3068\u306f\u5404\u3005\u306e\u88c1\u91cf\u3067\u4e57\u308a\u5207\u3063\u3066\u304f\u3060\u3055\u3044\u3088\u3081\u3093\u3069\u304f\u3055\u3044\u306a RT @kihirokiro: Twitter\u3067\u30cd\u30bf\u30d0\u30ec\u4e91\u3005\u3044\u3063\u3066\u3082\u5143\u3005Twitter\u3063\u3066\u300c\u500b\u4eba\u306e\u72ec\u308a\u8a00\u300d\u3060\u308d","id":41190073227550720,"from_user_id":49219631,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://sourceforge.jp/projects/tween/wiki/FrontPage&quot; rel=&quot;nofollow&quot;&gt;Tween&lt;/a&gt;"},{"from_user_id_str":"98560114","profile_image_url":"http://a0.twimg.com/profile_images/1252332545/IMGP8170_normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:32 +0000","from_user":"qess0093","id_str":"41190070706905088","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u30b7\u30e5\u30fc\u30c6\u30a3\u304f\u305d\u3046\u305c\u3048\uff57 RT @syu_thi_bot: \u3044\u3064\u307e\u3067Twitter\u3084\u3063\u3066\u308b\u3093\u3060\u3044\uff1f\u3044\u3044\u52a0\u6e1b\u73fe\u5b9f\u306b\u623b\u308a\u306a\u3088\u3002\u3053\u3053\u306f\u30ad\u30df\u305f\u3061\u4e09\u6b21\u5143\u306e\u4eba\u9593\u304c\u3044\u308b\u3079\u304d\u5834\u6240\u3058\u3083\u306a\u3044\u3093\u3060\u3088\u3002\u305d\u3093\u306a\u306e\u57fa\u672c\u3060\u308d\uff01\uff01","id":41190070706905088,"from_user_id":98560114,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://sourceforge.jp/projects/tween/wiki/FrontPage&quot; rel=&quot;nofollow&quot;&gt;Tween&lt;/a&gt;"},{"from_user_id_str":"197459334","profile_image_url":"http://a3.twimg.com/profile_images/1210698414/shiratorishoko_normal.png","created_at":"Fri, 25 Feb 2011 17:37:31 +0000","from_user":"SyokoShiratori","id_str":"41190064243478528","metadata":{"result_type":"recent"},"to_user_id":null,"text":"+0.50kg \u540c\u3058\u304f\u30c0\u30a4\u30a8\u30c3\u30c8\u4e2d\u3067\u3059\u3002\u3088\u304f\u3053\u306e\u30b5\u30a4\u30c8\u3067\u4f53\u91cd\u5831\u544a\u3057\u3066\u307e\u3059\u266a  http://bit.ly/fhN2fM RT @msophiah \u3046\u3046\u3046\u30fb\u30fb\u30fb\u304a\u8179\u3059\u3044\u305f\u301c\u30fb\u30fb\u30fb\u6211\u6162\u3001\u6211\u6162\u3001\u6211\u6162\u3002\u30c0\u30a4\u30a8\u30c3\u30c8\u3001\u30c0\u30a4\u30a8\u30c3\u30c8\u3001\u30c0\u30a4\u30a8\u30c3\u30c8\u3002","id":41190064243478528,"from_user_id":197459334,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://mob-mc.com&quot; rel=&quot;nofollow&quot;&gt;\uff08\u4eee\u79f0\uff09\u30c4\u30a4\u30af\u30ea\u30c3\u30af twiclick&lt;/a&gt;"},{"from_user_id_str":"180410237","profile_image_url":"http://a3.twimg.com/profile_images/1254473979/icon_normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:30 +0000","from_user":"ka_ph","id_str":"41190062184079360","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3010\u5b9a\u671fpost\u3011\u30d5\u30a7\u30eb\u30c7\u30a3\u30ca\u30f3\u30c8bot( http://twitter.com/ferdinand_bot )\u4f5c\u308a\u307e\u3057\u305f\u3002\u304a\u5b50\u69d8\u306e\u540d\u524d\u304a\u501f\u308a\u3055\u305b\u3066\u304f\u308c\u308b\u65b9\u3044\u307e\u3057\u305f\u3089@ka_ph\u307e\u3067\u304a\u9858\u3044\u3057\u307e\u3059\u3002","id":41190062184079360,"from_user_id":180410237,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twittbot.net/&quot; rel=&quot;nofollow&quot;&gt;twittbot.net&lt;/a&gt;"},{"from_user_id_str":"52283906","profile_image_url":"http://a3.twimg.com/profile_images/387644016/10100654519_s_normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:26 +0000","from_user":"rolling_bean","id_str":"41190043313913856","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3081\u307e\u3044\u304c\u3057\u307e\u3059\u306d\u301cRT @yurikalin:\u30b3\u30a4\u30c4\u30e9\u5168\u54e1\u3044\u306a\u304f\u306a\u3063\u305f\u3089\u3001\u660e\u308b\u304f\u306a\u308b\u3060\u308d\u3046\u306a\u3041\uff5e\u266a\uff1e\u65e5\u672c\u306e\u30d3\u30b8\u30e7\u30f3 RT roll \u65e5\u7d4c\uff06CSIS\u30b7\u30f3\u30dd\u30b8\u30a6\u30e0\u300c\u5b89\u4fdd\u6539\u5b9a50\u5468\u5e74\u3001\u3069\u3046\u306a\u308b\u65e5\u7c73\u95a2\u4fc2\u300d http://bit.ly/dES8PY\u3000http://bit.ly/ihg0GT","id":41190043313913856,"from_user_id":52283906,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://tabtter.jp&quot; rel=&quot;nofollow&quot;&gt;\u30bf\u30d6\u30c3\u30bf\u30fc&lt;/a&gt;"},{"from_user_id_str":"351896","profile_image_url":"http://a3.twimg.com/profile_images/1179865336/icon12911887173020_normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:26 +0000","from_user":"cicada","id_str":"41190043292925952","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u4e2d\u5b66\u53d7\u9a13\u7a0b\u5ea6\u306e\u7b97\u6570\u306e\u554f\u984c\u3092\u51fa\u3059BOT http://twitter.com/arithmetic_bot  \u6c17\u306b\u306f\u306a\u308b\u304c\u3001\u3061\u3083\u3093\u3068\u898b\u308b\u65e5\u304c\u6765\u308b\u3060\u308d\u3046\u304b\u30fb\u30fb\u3000#tearai\uff1a\u30ed\u30b3\u30e9\u30dc\u5bae\u5d0e\u770c http://locolabo.com/mz/ #mzlf","id":41190043292925952,"from_user_id":351896,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://locolabo.com/mz/&quot; rel=&quot;nofollow&quot;&gt;\u30ed\u30b3\u30e9\u30dc\u5bae\u5d0e\u770c&lt;/a&gt;"},{"from_user_id_str":"744554","profile_image_url":"http://a1.twimg.com/profile_images/1088753125/11868894_normal.gif","created_at":"Fri, 25 Feb 2011 17:37:20 +0000","from_user":"Febreze","id_str":"41190019020505088","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u30cd\u30bf\u30d0\u30ec\u3042\u307e\u308a\u6c17\u306b\u3057\u306a\u3044\u30bf\u30a4\u30d7\u3060\u3051\u3069\u306a\u3093\u304b\u3053\u3046Twitter\u958b\u304f\u3060\u3051\u3067\u30ac\u30f3\u30ac\u30f3\u30cd\u30bf\u30d0\u30ec\u5165\u3063\u3066\u304f\u308b\u306e\u306f\u306a\u3093\u3068\u3082\u8a00\u3048\u306a\u3044\u306a\u3002","id":41190019020505088,"from_user_id":744554,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"203371794","profile_image_url":"http://a1.twimg.com/profile_images/1226537594/___normal.png","created_at":"Fri, 25 Feb 2011 17:37:16 +0000","from_user":"himeko24","id_str":"41190004139114496","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6b21\u3044\u3063\u3066\u307f\u3088\u30fc\u3000\u306f\u3044\u3053\u308c\u3000 \u6fc0\u5b89\u26052010\u5e74\u590f\u65b0\u30c7\u30b6\u30a4\u30f3\u2605\u30bb\u30af\u30b7\u30fc\u306a\u9023\u4f53\u5f0f\u7121\u5730\u6c34\u7740 \u80f8\u30d1\u30c3\u30c9\u4ed8\u304dQ122 http://bit.ly/h72C4i","id":41190004139114496,"from_user_id":203371794,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.google.co.jp&quot; rel=&quot;nofollow&quot;&gt;himeko24&lt;/a&gt;"},{"from_user_id_str":"119988932","profile_image_url":"http://a3.twimg.com/profile_images/1214344633/image_normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:16 +0000","from_user":"7na_love_6sa","id_str":"41190002000007169","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u30d6\u30ed\u30b0\u66f4\u65b0\u3057\u307e\u3057\u305f\uff01Twitter\uff06mixi\u304b\u3089\u3082\u30b3\u30e1\u30f3\u30c8\u5b9c\u3057\u304f\u306d\u266a\n\u300c\u30cd\u30a4\u30eb\u30c1\u30a7\u30f3\u30b8\u266a\u300d http://amba.to/hy0Do2","id":41190002000007169,"from_user_id":119988932,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"217408835","profile_image_url":"http://a1.twimg.com/profile_images/1215428655/____4.0157_normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:12 +0000","from_user":"nikubenki1123","id_str":"41189985424125952","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @x68k: \u57fa\u672c\u7684\u306bTwitter\u4e0a\u3067\u306f&quot;\u597d\u304d\u306b\u3084\u308c\u3070\u3044\u3044&quot;\u3060\u3051\u3069\u3001\u540d\u524d\u3082\u30a2\u30a4\u30b3\u30f3\u3082\u30a2\u30f3\u30bf\u3058\u3083\u306a\u3044\u305f\u304f\u3055\u3093\u306e\u4eba\u305f\u3061\u306e\u9b42\u304c\u3053\u3082\u3063\u305f\u3082\u306e\u306a\u306e\u3060\u304b\u3089\u3001\u305d\u308c\u3060\u3051\u306f&quot;\u80cc\u8ca0\u3048&quot;\u3068\u601d\u3046\uff1e\u30ad\u30e3\u30e9\u30af\u30bf\u30fc\u30a2\u30ab\u30a6\u30f3\u30c8","id":41189985424125952,"from_user_id":217408835,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://tweetlogix.com&quot; rel=&quot;nofollow&quot;&gt;Tweetlogix&lt;/a&gt;"},{"from_user_id_str":"139137996","profile_image_url":"http://a0.twimg.com/profile_images/1087408006/cut_work_18_normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:12 +0000","from_user":"fujiokayouko","id_str":"41189984354566144","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u591c\u4e2d\u306bTwitter\u3092\u306a\u3093\u3068\u306a\u304f\u8997\u3044\u305f\u3089\u3082\u306e\u3059\u3054\u3044\u307e\u3069\u30de\u30aeTL\u3067","id":41189984354566144,"from_user_id":139137996,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://mobile.twitter.com&quot; rel=&quot;nofollow&quot;&gt;Mobile Web&lt;/a&gt;"},{"from_user_id_str":"61221002","profile_image_url":"http://a3.twimg.com/profile_images/554916301/_1259757374_61_normal.png","created_at":"Fri, 25 Feb 2011 17:37:08 +0000","from_user":"hiromk63","id_str":"41189970613903360","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @kenji_kohashi \u7121\u4e8b\u6620\u753b\u88fd\u4f5c\u306e\u5831\u544a\u306f\u3067\u304d\u305f\u3051\u3069\u4eca\u3060\u7de8\u96c6\u306f\u7d9a\u3044\u3066\u307e\u3059w \u305d\u3093\u306a\u3068\u3053\u3067 \u6620\u753b\u300cDON'T STOP!\u300d\u306eTwitter \u30a2\u30ab\u30a6\u30f3\u30c8 @DONTSTOPMOVIE \u3082\u958b\u59cb\u3001\u3082\u3057\u826f\u304b\u3063\u305f\u3089\u30d5\u30a9\u30ed\u30fc\u3057\u3066\u304f\u3060\u3055\u3044\uff01\u50d5\u3082\u542b\u3081\u6620\u753b\u88fd\u4f5c\u95a2\u4fc2\u8005\u304c\u6c17\u9577\u306b\u3064\u3076\u3084\u304d\u307e\u3059","id":41189970613903360,"from_user_id":61221002,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;"},{"from_user_id_str":"147393812","profile_image_url":"http://a1.twimg.com/profile_images/1114001122/hana_10_normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:07 +0000","from_user":"tubasa_uki","id_str":"41189965924667392","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\uff10\uff10\uff17\u3082\u3073\u3063\u304f\u308a\u3000\u5927\u56fd\u306e\u5927\u7d71\u9818\u9078\u304b\u3089\u3000\u5cf6\u56fd\u306e\u5730\u65b9\u9078\u306e\u9078\u6319\u307e\u3067\u306b\u3082\u3000\u6697\u8e8d\u3057\u3066\u3044\u308b\uff3e\uff3e\u3000\u3053\u306eTwitter \u30b9\u30ad\u30e3\u30f3\u30c0\u30eb\u3084\u60aa\u8cea\u306a\u4e8b\u4ef6\u306b\u3082\u7d61\u3080\u304c\u3000Twitter\u304b\u3089\u767a\u4fe1\u3055\u308c\u305f\u3000\u5e73\u548c\u3078\u306e\u8ca2\u732e\u306f\u3000\u307e\u3055\u306b\u30ce\u30fc\u3079\u30eb\u5e73\u548c\u8cde\u3082\u306e http://bit.ly/esjWDG #dotubo_ss","id":41189965924667392,"from_user_id":147393812,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twittbot.net/&quot; rel=&quot;nofollow&quot;&gt;twittbot.net&lt;/a&gt;"},{"from_user_id_str":"96081746","profile_image_url":"http://a0.twimg.com/profile_images/1244505419/illust832_normal.png","created_at":"Fri, 25 Feb 2011 17:37:07 +0000","from_user":"kazukt","id_str":"41189963131387904","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3046\u308b\u304a\u307c\u7d75\u3001\u4ed6\u306e\u4eba\u306e\u3082\u898b\u3066\u3044\u305f\u3089\u3084\u305f\u3089\u30de\u30f3\u30ac\u306e\u30ad\u30e3\u30e9\u30af\u30bf\u30fc\u3067\u3080\u3061\u3083\u304f\u3061\u3083\u4e0a\u624b\u3044\u4eba\u304c\uff01\u2026\u3068\u3001\u30d7\u30ed\u30d5\u30a3\u30fc\u30eb\u898b\u305f\u3089\u3001\u306a\u3093\u3068\u3054\u672c\u4eba\u304c\u66f8\u3044\u3066\u3044\u3089\u3063\u3057\u3083\u3063\u305f\u3002Twitter\u3063\u3066\u30b9\u30b4\u30a4\u308f\u3002","id":41189963131387904,"from_user_id":96081746,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://janetter.net/&quot; rel=&quot;nofollow&quot;&gt;Janetter&lt;/a&gt;"},{"from_user_id_str":"86306230","profile_image_url":"http://a1.twimg.com/profile_images/593004766/j2j_normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:03 +0000","from_user":"excite_j2j","id_str":"41189949927596032","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u79c1\u306f\u3001\u65e9\u7a32\u7530\u5927\u5b66\u30aa\u30fc\u30d7\u30f3\u30ab\u30ec\u30c3\u30b8\u306e\u5b66\u751f\u306e\u8003\u3048\u3066\u3044\u308b\u3001\u5352\u696d\u751fSoudai /\u65e9\u7a32\u7530\u30ab\u30fc\u30c9\u4f1a\u54e1/ Soudai\u5b66\u751f\u89aa/\u3001\u8ab0\u304b\u304c\u30aa\u30fc\u30d7\u30f3\u30ab\u30ec\u30c3\u30b8\u65e9\u7a32\u7530\u5927\u5b662000\u5186\u306e\u5165\u5834\u6599\u306e\u30e1\u30f3\u30d0\u30fc\u3092\u7d39\u4ecb\u3057\u3066\u304f\u308c\u305f\u65b9\u304c\u305a\u3063\u3068\u5b89\u4e0a\u304c\u308a\u3060\u3002 (\u5143\u767a\u8a00 http://bit.ly/esxiwT )","id":41189949927596032,"from_user_id":86306230,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://d.hatena.ne.jp/fn7&quot; rel=&quot;nofollow&quot;&gt;\u65e5\u672c\u8a9e\u65e5\u672c\u8a9e\u7ffb\u8a33\u30b8\u30a7\u30cd\u30ec\u30fc\u30bf&lt;/a&gt;"},{"from_user_id_str":"120927161","profile_image_url":"http://a0.twimg.com/profile_images/1230006483/20110131_13022_26776_normal.jpg","created_at":"Fri, 25 Feb 2011 17:37:00 +0000","from_user":"FRISKFOSSILFANG","id_str":"41189933855158272","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @syu_thi_bot: \u3044\u3064\u307e\u3067Twitter\u3084\u3063\u3066\u308b\u3093\u3060\u3044\uff1f\u3044\u3044\u52a0\u6e1b\u73fe\u5b9f\u306b\u623b\u308a\u306a\u3088\u3002\u3053\u3053\u306f\u30ad\u30df\u305f\u3061\u4e09\u6b21\u5143\u306e\u4eba\u9593\u304c\u3044\u308b\u3079\u304d\u5834\u6240\u3058\u3083\u306a\u3044\u3093\u3060\u3088\u3002\u305d\u3093\u306a\u306e\u57fa\u672c\u3060\u308d\uff01\uff01","id":41189933855158272,"from_user_id":120927161,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twittbot.net/&quot; rel=&quot;nofollow&quot;&gt;twittbot.net&lt;/a&gt;"},{"from_user_id_str":"228973151","profile_image_url":"http://a1.twimg.com/sticky/default_profile_images/default_profile_0_normal.png","created_at":"Fri, 25 Feb 2011 17:36:59 +0000","from_user":"daisuke1589","id_str":"41189931887890432","metadata":{"result_type":"recent"},"to_user_id":null,"text":"twitter\u3063\u3066\u52dd\u624b\u306b\u30d5\u30a9\u30ed\u30fc\u3057\u3066\u3082\u3044\u3044\u3093\u3060\u3063\u3051\uff1f","id":41189931887890432,"from_user_id":228973151,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"54314110","profile_image_url":"http://a2.twimg.com/profile_images/1193541240/image_normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:57 +0000","from_user":"Alohazuki","id_str":"41189922668953600","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Twitter\u306e\u5e83\u5cf6\u5f01\u3082\u3042\u3063\u305f\u3093\u3060\u306d\u3002","id":41189922668953600,"from_user_id":54314110,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.flight.co.jp/iPhone/TweetMe/&quot; rel=&quot;nofollow&quot;&gt;TweetMe for iPhone&lt;/a&gt;"},{"from_user_id_str":"72594681","profile_image_url":"http://a3.twimg.com/profile_images/1227240916/____normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:56 +0000","from_user":"uri4ichi","id_str":"41189916922744832","metadata":{"result_type":"recent"},"to_user_id":226852303,"text":"@7_fuka Twitter\u304b\u3076\u308c\u306f\u3001\u3059\u3050\u306bD\u306b\u8d70\u308b\u306e\u3067\u3002\u306a\u3093\u3060\u304b\u5fae\u7b11\u307e\u3057\u3044\u306e\u3067\u3059( \u2579\u25e1\u2579)","id":41189916922744832,"from_user_id":72594681,"to_user":"7_fuka","geo":null,"iso_language_code":"ja","to_user_id_str":"226852303","source":"&lt;a href=&quot;http://sites.google.com/site/yorufukurou/&quot; rel=&quot;nofollow&quot;&gt;YoruFukurou&lt;/a&gt;"},{"from_user_id_str":"168393854","profile_image_url":"http://a3.twimg.com/profile_images/1253554518/GuNp3xVS_normal","created_at":"Fri, 25 Feb 2011 17:36:55 +0000","from_user":"makonyanhime","id_str":"41189913701515264","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6589\u85e4\u3055\u3093\u3082\u30c0\u30e1\u3060...\u4eca\u591c\u306fTwitter\u5909\u3060\u306a\u3041(;_;)","id":41189913701515264,"from_user_id":168393854,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://mobile.twitter.com&quot; rel=&quot;nofollow&quot;&gt;Twitter for Android&lt;/a&gt;"},{"from_user_id_str":"122636787","profile_image_url":"http://a2.twimg.com/profile_images/1189607563/DSC01987_normal.JPG","created_at":"Fri, 25 Feb 2011 17:36:54 +0000","from_user":"enpy1217","id_str":"41189908500594688","metadata":{"result_type":"recent"},"to_user_id":null,"text":"twitter\u4e45\u3057\u3076\u308a\u306b\u958b\u3044\u305f\u3002\u3068\u3066\u3082\u591a\u5fd9\u3060\u3063\u305f\u3002\u75b2\u308c\u305f\u3002\u304a\u98a8\u5442\u306b\u3082\u5165\u308c\u306a\u304b\u3063\u305f\u3002\u30ad\u30bf\u30cd\u2026","id":41189908500594688,"from_user_id":122636787,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.flight.co.jp/iPhone/TweetMe/&quot; rel=&quot;nofollow&quot;&gt;TweetMe for iPhone&lt;/a&gt;"},{"from_user_id_str":"81663694","profile_image_url":"http://a3.twimg.com/profile_images/1177576126/__normal.png","created_at":"Fri, 25 Feb 2011 17:36:51 +0000","from_user":"progmiya","id_str":"41189895494049792","metadata":{"result_type":"recent"},"to_user_id":94050844,"text":"@hirasai_skyhigh \u6674\u308c\u541b\u3002\u30cd\u30bf\u30d0\u30ec\u306f\u898b\u305f\u304f\u306a\u3044\u3051\u3069\u30012ch\u3082twitter\u3082\u3084\u308a\u305f\u3044\u306e\u304c\u4eba\u3068\u8a00\u3046\u3082\u306e\u3088","id":41189895494049792,"from_user_id":81663694,"to_user":"hirasai_skyhigh","geo":null,"iso_language_code":"ja","to_user_id_str":"94050844","source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"159237469","profile_image_url":"http://a3.twimg.com/profile_images/1140433484/______2_normal.png","created_at":"Fri, 25 Feb 2011 17:36:50 +0000","from_user":"hikari_juku","id_str":"41189892096532480","metadata":{"result_type":"recent"},"to_user_id":135634241,"text":"@sssukimasuky \n\u4f55\u304b\u3042\u3063\u305f\u98a8\u3067\u3059\u306d\uff08\u7b11\uff09\u3000\u78ba\u304b\u306bTwitter\u306f\u6c17\u8efd\u3067\u3059\u3057\u306d\u3002\u30e1\u30fc\u30eb\u306b\u306a\u308b\u3068\u30d5\u30c3\u30c8\u30ef\u30fc\u30af\u304c\u91cd\u304f\u306a\u308b\u5834\u5408\u3082\u3042\u308b\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u306d\u3002\u305d\u3046\u8003\u3048\u308b\u3068\u3059\u3054\u3044\u6642\u4ee3\u3060\u3002","id":41189892096532480,"from_user_id":159237469,"to_user":"sssukimasuky","geo":null,"iso_language_code":"ja","to_user_id_str":"135634241","source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;"},{"from_user_id_str":"141915990","profile_image_url":"http://a0.twimg.com/profile_images/1248969898/icon679566266564378764images_normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:48 +0000","from_user":"adashino_","id_str":"41189886878949376","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @fuefukioyasumi: \u3082\u3046\u3044\u3044\u3002\u300c\u671d\u751f\u300d\u306a\u3093\u304b\u3044\u3044\u3002BBC\u3082CNN\u3082\u82f1\u8a9e\u653e\u9001\u3060\u3002\u307f\u3093\u306a\u3001\u30c6\u30ec\u30d3\u3092\u3076\u3061\u3063\u3068\u5207\u3063\u3066Twitter\u3092\u898b\u3088\u3046\u3002\u3053\u3053\u306b\u300c\u751f\u304d\u305f\u60c5\u5831\u300d\u304c\u6d41\u308c\u3066\u3044\u308b\u3002\u300c\u751f\u304d\u305f\u53eb\u3073\u300d\u304c\u3042\u308b\u3002\u3053\u3053\u304b\u3089\u3067\u3082\u6b74\u53f2\u306e\u8ee2\u63db\u70b9\u3092\u611f\u3058\u308b\u4e8b\u304c\u3067\u304d\u308b\u306e\u3060\u3002 @gjmorley #libjp","id":41189886878949376,"from_user_id":141915990,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"111308629","profile_image_url":"http://a1.twimg.com/profile_images/1137421845/mmooton_normal.png","created_at":"Fri, 25 Feb 2011 17:36:46 +0000","from_user":"mmooton","id_str":"41189877940748288","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @minponjp: \u3010\u3064\u3076\u3084\u304f\u3060\u3051\u306710000\u5186\u5546\u54c1\u5238GET\u3011&lt;&lt;visa\u30ae\u30d5\u30c8\u30ab\u30fc\u30c910000\u5186\u5206\u3092\u6bce\u6708\u62bd\u9078\u3067\u30d7\u30ec\u30bc\u30f3\u30c8&gt;&gt; \u21d2\u8a73\u3057\u304f\u306f\u4e0b\u306e\uff35\uff32\uff2c\u3088\u308a\u2193\u2193\u2193\u2193\u2193 http://minpon.jp/user_data/twitter_campaign.php","id":41189877940748288,"from_user_id":111308629,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twittbot.net/&quot; rel=&quot;nofollow&quot;&gt;twittbot.net&lt;/a&gt;"},{"from_user_id_str":"140945098","profile_image_url":"http://a0.twimg.com/profile_images/1198948281/b7664c71-87bc-4187-9d46-a61ab8d41b96_normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:46 +0000","from_user":"waruimayuko","id_str":"41189877122867200","metadata":{"result_type":"recent"},"to_user_id":113371008,"text":"@toumeisyoujyo \u3042\u3053\u3061\u3083\u3093Twitter\u3084\u3063\u3066\u305f\u306e\u306d\u3002\u307e\u3060\u304a\u5e2d\u3042\u308b\u305d\u3046\u306a\u306e\u3067\u662f\u975e\uff01","id":41189877122867200,"from_user_id":140945098,"to_user":"toumeisyoujyo","geo":null,"iso_language_code":"ja","to_user_id_str":"113371008","source":"&lt;a href=&quot;http://twimi.jp/?r=via&quot; rel=&quot;nofollow&quot;&gt;twimi\u2606new&lt;/a&gt;"},{"from_user_id_str":"167512527","profile_image_url":"http://a1.twimg.com/profile_images/1172286365/newspaper_normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:45 +0000","from_user":"KoranKaget","id_str":"41189871775252480","metadata":{"result_type":"recent"},"to_user_id":null,"text":"http://bit.ly/93Eeud RT @motocentrism \u30d6\u30ed\u30b0\u8a18\u4e8b\u66f8\u304d\u307e\u3057\u305f\u30fc\uff1a MotoGP\u3000\u30d6\u30c3\u30af\u30e1\u30fc\u30ab\u30fc\u306b\u898b\u308b\u5404\u30e9\u30a4\u30c0\u30fc\u306e\u4e0b\u99ac\u8a55 - http://goo.gl/xVe6... http://bit.ly/hDYxyx @motogpudpate","id":41189871775252480,"from_user_id":167512527,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"160251772","profile_image_url":"http://a3.twimg.com/profile_images/1150692471/_____normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:45 +0000","from_user":"keikochaki","id_str":"41189870781075456","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u300c\u305a\u3063\u3068twitter\u3084\u3063\u3066\u308b\u3088\u306d\u300d\u3068\u6012\u3089\u308c\u3066\u3057\u307e\u3063\u305f(\u82e6\u7b11)","id":41189870781075456,"from_user_id":160251772,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twtr.jp&quot; rel=&quot;nofollow&quot;&gt;Keitai Web&lt;/a&gt;"},{"from_user_id_str":"211565766","profile_image_url":"http://a2.twimg.com/sticky/default_profile_images/default_profile_5_normal.png","created_at":"Fri, 25 Feb 2011 17:36:42 +0000","from_user":"sayap1yo","id_str":"41189858236043264","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u304a\u98df\u4e8b\u4f1a\u884c\u304d\u305f\u304b\u3063\u305f\u3051\u3069\n\u53cb\u9054\u3068\u904a\u3093\u3067\u307e\u3093\u305f\ue056\ue326\n\nTwitter\u898b\u3066\u307e\u3057\u305f\u3051\u3069\n\u3064\u3076\u3084\u304f\u30bf\u30a4\u30df\u30f3\u30b0\n\u5931\u3063\u3066\u305f\u3060\u3051\u3067\u3059( \u00b4 \u25bd ` )\uff89\u7b11","id":41189858236043264,"from_user_id":211565766,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.nibirutech.com&quot; rel=&quot;nofollow&quot;&gt;TwitBird&lt;/a&gt;"},{"from_user_id_str":"19603191","profile_image_url":"http://a1.twimg.com/profile_images/1254077264/116_normal.gif","created_at":"Fri, 25 Feb 2011 17:36:40 +0000","from_user":"Purple_Flash","id_str":"41189851873157120","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @syu_thi_bot: \u3044\u3064\u307e\u3067Twitter\u3084\u3063\u3066\u308b\u3093\u3060\u3044\uff1f\u3044\u3044\u52a0\u6e1b\u73fe\u5b9f\u306b\u623b\u308a\u306a\u3088\u3002\u3053\u3053\u306f\u30ad\u30df\u305f\u3061\u4e09\u6b21\u5143\u306e\u4eba\u9593\u304c\u3044\u308b\u3079\u304d\u5834\u6240\u3058\u3083\u306a\u3044\u3093\u3060\u3088\u3002\u305d\u3093\u306a\u306e\u57fa\u672c\u3060\u308d\uff01\uff01","id":41189851873157120,"from_user_id":19603191,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twittbot.net/&quot; rel=&quot;nofollow&quot;&gt;twittbot.net&lt;/a&gt;"},{"from_user_id_str":"215930291","profile_image_url":"http://a0.twimg.com/profile_images/1247906106/image_normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:30 +0000","from_user":"minori_millie","id_str":"41189810055938048","metadata":{"result_type":"recent"},"to_user_id":212972774,"text":"@c_c_chika \u7b11\u3063\u3066\u305d\u3046\u3060\u306a\u3063\u3066\u601d\u3063\u3066\u305f(o^^o)\u30c1\u30ab\u306e\u7b11\u3044\u58f0\u304c\u805e\u3053\u3048\u3066\u304d\u305f\u3082\u3093\u3002mail\u306bTwitter\u3067\u5927\u5fd9\u3057\u3084\u3002","id":41189810055938048,"from_user_id":215930291,"to_user":"c_c_chika","geo":null,"iso_language_code":"ja","to_user_id_str":"212972774","source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"197459334","profile_image_url":"http://a3.twimg.com/profile_images/1210698414/shiratorishoko_normal.png","created_at":"Fri, 25 Feb 2011 17:36:30 +0000","from_user":"SyokoShiratori","id_str":"41189809586311168","metadata":{"result_type":"recent"},"to_user_id":null,"text":"+0.90kg \u30c0\u30a4\u30a8\u30c3\u30c8\u5fdc\u63f4\u3057\u3066\u307e\u3059\u3002(*^^*)\u30c0\u30a4\u30a8\u30c3\u30c8\u4f53\u91cd\u5831\u544a\u306e\u3064\u3076\u3084\u304d\u3067\u3059\u3002 http://bit.ly/fhN2fM RT @hayayumi \u3053\u3093\u306a\u6642\u9593\u306b\u3084\u304d\u306b\u304f\u30fc\u266b\u30c0\u30a4\u30a8\u30c3\u30c8\u671f\u9593\u306a\u306e\u306b\u306d....","id":41189809586311168,"from_user_id":197459334,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://mob-mc.com&quot; rel=&quot;nofollow&quot;&gt;\uff08\u4eee\u79f0\uff09\u30c4\u30a4\u30af\u30ea\u30c3\u30af twiclick&lt;/a&gt;"},{"from_user_id_str":"12337665","profile_image_url":"http://a0.twimg.com/profile_images/1146194926/pixiv-icon_normal.png","created_at":"Fri, 25 Feb 2011 17:36:27 +0000","from_user":"t_a_k_i","id_str":"41189797364121600","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Google\u30ea\u30fc\u30c0\u30fc\u3067\u30b5\u30a4\u30c8\u306e\u66f4\u65b0\u30c1\u30a7\u30c3\u30af\u3059\u308b\u3088\u308a\u3001Twitter\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u30d5\u30a9\u30ed\u30fc\u3057\u3066\u66f4\u65b0\u3092\u77e5\u308b\u307b\u3046\u304c\u4fbf\u5229\u3060\u306a\u3053\u308c","id":41189797364121600,"from_user_id":12337665,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://sites.google.com/site/yorufukurou/&quot; rel=&quot;nofollow&quot;&gt;YoruFukurou&lt;/a&gt;"},{"from_user_id_str":"104862453","profile_image_url":"http://a3.twimg.com/profile_images/1248609864/Image014_normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:25 +0000","from_user":"yuz_ume","id_str":"41189787943714816","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @fuefukioyasumi: \u3082\u3046\u3044\u3044\u3002\u300c\u671d\u751f\u300d\u306a\u3093\u304b\u3044\u3044\u3002BBC\u3082CNN\u3082\u82f1\u8a9e\u653e\u9001\u3060\u3002\u307f\u3093\u306a\u3001\u30c6\u30ec\u30d3\u3092\u3076\u3061\u3063\u3068\u5207\u3063\u3066Twitter\u3092\u898b\u3088\u3046\u3002\u3053\u3053\u306b\u300c\u751f\u304d\u305f\u60c5\u5831\u300d\u304c\u6d41\u308c\u3066\u3044\u308b\u3002\u300c\u751f\u304d\u305f\u53eb\u3073\u300d\u304c\u3042\u308b\u3002\u3053\u3053\u304b\u3089\u3067\u3082\u6b74\u53f2\u306e\u8ee2\u63db\u70b9\u3092\u611f\u3058\u308b\u4e8b\u304c\u3067\u304d\u308b\u306e\u3060\u3002 @gjmorley #libjp","id":41189787943714816,"from_user_id":104862453,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"213846746","profile_image_url":"http://a1.twimg.com/profile_images/1242263342/haruhi1_normal.png","created_at":"Fri, 25 Feb 2011 17:36:23 +0000","from_user":"sloth888jpgame","id_str":"41189778409922560","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u643a\u5e2f\u96fb\u8a71\u7528\u306eTwitter\u3092\u5229\u7528\u3057\u305f\u30b2\u30fc\u30e0\u3092\u77e5\u3063\u3066\u3044\u308b\u4eba\u306f\u3001\u7d39\u4ecb\u3057\u3066\u304f\u3060\u3055\u3044\u3002 #TwitterGame","id":41189778409922560,"from_user_id":213846746,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twittbot.net/&quot; rel=&quot;nofollow&quot;&gt;twittbot.net&lt;/a&gt;"},{"from_user_id_str":"17484724","profile_image_url":"http://a2.twimg.com/profile_images/1138677235/iconue_normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:22 +0000","from_user":"tsutcho","id_str":"41189777831231490","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u30a2\u30aa\u30b7\u30de\u3055\u3093twitter\u306b\u7acb\u3064\u30fb\u30fb\u30fb\u3060\u3068\u30fb\u30fb\u30fb\uff1f\uff12\u756a\u76ee\u306e\u30d5\u30a9\u30ed\u30ef\u30fc\u306e\u5ea7\u3092\u3044\u305f\u3060\u3044\u3066\u304a\u3053\u3046","id":41189777831231490,"from_user_id":17484724,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"220041064","profile_image_url":"http://a1.twimg.com/sticky/default_profile_images/default_profile_0_normal.png","created_at":"Fri, 25 Feb 2011 17:36:21 +0000","from_user":"sloth888jp_bot5","id_str":"41189772521254914","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6642\u3005\u3001TwiAll\uff08\u30c4\u30a4\u30aa\u30fc\u30eb\uff09\u3067Twitter\u306e\u81ea\u52d5\u30d5\u30a9\u30ed\u30fc\u30fb\u30d5\u30a9\u30ed\u30fc\u8fd4\u3057\u3092\u3057\u3066\u3044\u307e\u3059\u3002","id":41189772521254914,"from_user_id":220041064,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twittbot.net/&quot; rel=&quot;nofollow&quot;&gt;twittbot.net&lt;/a&gt;"},{"from_user_id_str":"177693537","profile_image_url":"http://a0.twimg.com/profile_images/1240130924/20110207213206_normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:21 +0000","from_user":"Na_Okinawa","id_str":"41189769912397824","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @ebizo66: \u30ea\u30d3\u30a2\u30fb\u30c8\u30ea\u30dd\u30ea\u306eTw\uff1a\u6551\u6025\u8eca\u304c\u6765\u305f\u304c\u3001\u8ca0\u50b7\u8005\u3092\u8eca\u5185\u3067\u6bba\u5bb3\u3057\u3066\u3044\u305f\u3001\u3068\u3002\uff08\u6ec5\u8336\u82e6\u8336\u3060\uff01\uff09http://ow.ly/43pQ2 #libjp","id":41189769912397824,"from_user_id":177693537,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;"},{"from_user_id_str":"147006592","profile_image_url":"http://a2.twimg.com/profile_images/1251306448/image_normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:19 +0000","from_user":"takasu_rika","id_str":"41189763532865536","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @kihirokiro: Twitter\u3067\u30cd\u30bf\u30d0\u30ec\u4e91\u3005\u3044\u3063\u3066\u3082\u5143\u3005Twitter\u3063\u3066\u300c\u500b\u4eba\u306e\u72ec\u308a\u8a00\u300d\u3060\u308d","id":41189763532865536,"from_user_id":147006592,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"100239985","profile_image_url":"http://a1.twimg.com/profile_images/733461294/CIMG0207_normal.JPG","created_at":"Fri, 25 Feb 2011 17:36:19 +0000","from_user":"strangebarjun","id_str":"41189762182152192","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Twitter\u3092\u59cb\u3081\u3066\u3001\u305d\u308d\u305d\u308d\u4e00\u5e74\u306b\u306a\u308a\u307e\u3059\u3002\u632f\u308a\u8fd4\u308b\u3068\u3001\u307c\u304f\u306e\u4eba\u9593\u6027\u3092\u305d\u3063\u304f\u308a\u53cd\u6620\u3059\u308b\u304c\u5982\u304f\u3001\u534a\u7aef\u3067\u3059\u306d\u3002\u60c5\u5831\u53ce\u96c6\u30c4\u30fc\u30eb\u3001\u60c5\u5831\u767a\u4fe1\u30c4\u30fc\u30eb\u3001\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3\u30c4\u30fc\u30eb\u3001\u3069\u308c\u3082\u6a5f\u80fd\u3057\u5207\u308c\u3066\u3044\u306a\u3044\u306a\u3042\u3001\u3068\u3002\u305d\u3057\u3066\u3042\u308b\u306e\u306f\u300c\u3069\u3053\u304b\u3089\u3082\u76f8\u624b\u306b\u3055\u308c\u3066\u3044\u306a\u3044\u300d\u3068\u3044\u3046\u3001\u65e2\u77e5\u306e\u73fe\u5b9f\u3067\u3059\u3002","id":41189762182152192,"from_user_id":100239985,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.echofon.com/&quot; rel=&quot;nofollow&quot;&gt;Echofon&lt;/a&gt;"},{"from_user_id_str":"126390384","profile_image_url":"http://a2.twimg.com/profile_images/1176065038/nemunemu_normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:18 +0000","from_user":"gesukapper","id_str":"41189757476286464","metadata":{"result_type":"recent"},"to_user_id":80935865,"text":"@tatsugorou \u306a\u3093\u304b\u4e16\u306e\u4e2d\u306b\u306f\u611a\u75f4\u3082\u4e0b\u30cd\u30bf\u3082\u8a00\u3044\u653e\u984c\u306etwitter\u3068\u3044\u3046\u3082\u306e\u304c\u3042\u308b\u305d\u3046\u3067\u3059\u3002","id":41189757476286464,"from_user_id":126390384,"to_user":"tatsugorou","geo":null,"iso_language_code":"ja","to_user_id_str":"80935865","source":"&lt;a href=&quot;http://janetter.net/&quot; rel=&quot;nofollow&quot;&gt;Janetter&lt;/a&gt;"},{"from_user_id_str":"196059029","profile_image_url":"http://a1.twimg.com/profile_images/1208831726/ruka2_normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:17 +0000","from_user":"luka_m_bot","id_str":"41189756666777600","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u308f\u305f\u3057\u3082twitter\u306f\u3058\u3081\u307e\u3057\u305f","id":41189756666777600,"from_user_id":196059029,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www24.atpages.jp/aoi2/bot.php&quot; rel=&quot;nofollow&quot;&gt;\u5de1\u308b\u97f3&lt;/a&gt;"},{"from_user_id_str":"88135613","profile_image_url":"http://a3.twimg.com/profile_images/1088552877/P9280150_normal.JPG","created_at":"Fri, 25 Feb 2011 17:36:14 +0000","from_user":"Roko38","id_str":"41189740321587200","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\uff46\uff42\u306b\u30ed\u30b0\u30a4\u30f3\u3059\u308b\u6a5f\u4f1a\u304c\u65e5\u306b\u65e5\u306b\u5897\u3048\u3066\u304d\u305f\uff3e\uff3e \u6163\u308c\u3066\u304f\u308b\u3068\u3042\u3063\u3061\u306e\u304c\u9762\u767d\u3044\uff06\u4f7f\u3044\u52dd\u624b\u304c\u3044\u3044\u3088\u3046\u306a\u6c17\u304c\u3059\u308b\u3002\u8907\u5408\u7684\u306b\u8272\u3005\u51fa\u6765\u307e\u3059\u3088\u306d\u2026\u3063\u3066\u3001\u601d\u3063\u305f\u3053\u3068\u3092\u545f\u304f\u306e\u306fTwitter\u3060\u3063\u305f\u308a\u3067\u3059\u304c\u2026(^_^; \u305d\u3057\u3066\u81ea\u5206\u306e\u5834\u5408\u306f\u30c4\u30a4\u3068\u9023\u52d5\u3057\u3065\u3089\u3044\u72b6\u6cc1\u3060\u3063\u305f\u308a\u3067\u2026\u3002","id":41189740321587200,"from_user_id":88135613,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"110825211","profile_image_url":"http://a2.twimg.com/profile_images/1108977866/o_t_normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:12 +0000","from_user":"kou_nanjyo","id_str":"41189731941363712","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @x68k: \u57fa\u672c\u7684\u306bTwitter\u4e0a\u3067\u306f&quot;\u597d\u304d\u306b\u3084\u308c\u3070\u3044\u3044&quot;\u3060\u3051\u3069\u3001\u540d\u524d\u3082\u30a2\u30a4\u30b3\u30f3\u3082\u30a2\u30f3\u30bf\u3058\u3083\u306a\u3044\u305f\u304f\u3055\u3093\u306e\u4eba\u305f\u3061\u306e\u9b42\u304c\u3053\u3082\u3063\u305f\u3082\u306e\u306a\u306e\u3060\u304b\u3089\u3001\u305d\u308c\u3060\u3051\u306f&quot;\u80cc\u8ca0\u3048&quot;\u3068\u601d\u3046\uff1e\u30ad\u30e3\u30e9\u30af\u30bf\u30fc\u30a2\u30ab\u30a6\u30f3\u30c8","id":41189731941363712,"from_user_id":110825211,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://tweetlogix.com&quot; rel=&quot;nofollow&quot;&gt;Tweetlogix&lt;/a&gt;"},{"from_user_id_str":"2095960","profile_image_url":"http://a2.twimg.com/profile_images/1248999075/majiresu2_normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:10 +0000","from_user":"guldeen","id_str":"41189726945947648","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3042\u308a\u3083\u307e\u3041\u3002\u25bchttp://bit.ly/et4vVa \uff3b\u89d2\u5ddd\u66f8\u5e97\uff3d\u300c\u30b6\u30fb\u30b9\u30cb\u30fc\u30ab\u30fc\u300d\u4f11\u520a\u3078\u3000\u300c\u6dbc\u5bae\u30cf\u30eb\u30d2\u300d\u751f\u3093\u3060\u30e9\u30ce\u30d9\u96d1\u8a8c18\u5e74\u3067\u5e55 via http://twitter.com/lkj777","id":41189726945947648,"from_user_id":2095960,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"164121549","profile_image_url":"http://a1.twimg.com/profile_images/1251758789/m_108-07ae3_normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:09 +0000","from_user":"hachi_touhi","id_str":"41189719299727360","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u30cd\u30bf\u30d0\u30ec\u6c17\u306b\u3057\u3066\u305f\u3089Twitter\u3084\u3063\u3066\u3089\u308c\u306a\u3044ww","id":41189719299727360,"from_user_id":164121549,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twicca.r246.jp/&quot; rel=&quot;nofollow&quot;&gt;twicca&lt;/a&gt;"},{"from_user_id_str":"194494659","profile_image_url":"http://a3.twimg.com/profile_images/1249717822/image_normal.jpg","created_at":"Fri, 25 Feb 2011 17:36:08 +0000","from_user":"tomonattomo","id_str":"41189715415801856","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3046\u308f\u3001\u660e\u65e5\u671d\u304b\u3089\u30d0\u30a4\u30c8\u3084\u306e\u306b\u306a\u3093\u3060\u3053\u306e\u306d\u3075\u304b\u3057\u3002\u3042\u3001\u591c\u66f4\u304b\u3057\u3002\u30d1\u30d4\u30eb\u30b9\u3068BUMP\u3001\u3048\u307f\u3068Twitter\u306eDM\u3057\u3059\u304e\u305f\u306a\u2026","id":41189715415801856,"from_user_id":194494659,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://mixi.jp/promotion.pl?id=voice_twitter&quot; rel=&quot;nofollow&quot;&gt; mixi \u30dc\u30a4\u30b9 &lt;/a&gt;"},{"from_user_id_str":"97224364","profile_image_url":"http://a3.twimg.com/profile_images/1150074355/fb8bcca60d340862c053a756377bfae8_normal.jpeg","created_at":"Fri, 25 Feb 2011 17:36:06 +0000","from_user":"xxxheat","id_str":"41189709174677504","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Twitter\u5b8c\u5168\u306b\u30d0\u30b0\u3063\u3066\u308borz","id":41189709174677504,"from_user_id":97224364,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"}],"max_id":41190705623732224,"since_id":38643906774044672,"refresh_url":"?since_id=41190705623732224&q=twitter","next_page":"?page=2&max_id=41190705623732224&rpp=100&lang=ja&q=twitter","results_per_page":100,"page":1,"completed_in":0.093336,"warning":"adjusted since_id to 38643906774044672 (), requested since_id was older than allowed -- since_id removed for pagination.","since_id_str":"38643906774044672","max_id_str":"41190705623732224","query":"twitter"}
+ test/json-data/jp50.json view
@@ -0,0 +1,1 @@+{"results":[{"from_user_id_str":"2458313","profile_image_url":"http://a2.twimg.com/profile_images/1203653060/fure091226_normal.png","created_at":"Thu, 27 Jan 2011 20:30:04 +0000","from_user":"19princess","id_str":"30724239224995840","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6d77\u6674\u300c\u672c\u65e5\u306e\u6771\u4eac\u306e\u304a\u5929\u6c17\u306f\u6674\u6642\u3005\u66c7\u3067\u3057\u3087\u3046\u3002\u6700\u9ad8\u6c17\u6e29\u306f8\u5ea6\u3001\u6700\u4f4e\u6c17\u6e29\u306f1\u5ea6\u3067\u3059\u3002\u3042\u306a\u305f\u306e\u4eca\u65e5\u306e\u4eba\u751f\u306b\u3068\u3073\u3063\u304d\u308a\u306e\u304a\u5929\u6c17\u3092\u2665\u300d","id":30724239224995840,"from_user_id":2458313,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://babyprincess.sakura.ne.jp/about/&quot; rel=&quot;nofollow&quot;&gt;\u5929\u4f7f\u5bb6\u306e\u88cf\u5c71&lt;/a&gt;"},{"from_user_id_str":"66578965","profile_image_url":"http://a0.twimg.com/profile_images/1183763267/fossetta_normal.png","created_at":"Thu, 27 Jan 2011 20:00:04 +0000","from_user":"Fossetta_Tokyo","id_str":"30716689779785729","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u304a\u306f\u3088\u3046\u3054\u3056\u3044\u307e\u3059!!\u6771\u4eac\u90fd\u3001\u672c\u65e5\u306e\u304a\u5929\u6c17\u306f\u6674\u6642\u3005\u66c7\u3002\u6700\u9ad8\u6c17\u6e298\u5ea6\u3001\u6700\u4f4e\u6c17\u6e291\u5ea6\u3002\u6771\u4eac\u5730\u65b9\u3067\u306f\u3001\u7a7a\u6c17\u306e\u4e7e\u71e5\u3057\u305f\u72b6\u614b\u304c\u7d9a\u3044\u3066\u3044\u307e\u3059\u3002\u706b\u306e\u53d6\u308a\u6271\u3044\u306b\u6ce8\u610f\u3057\u3066\u4e0b\u3055\u3044\u3002\u4f0a\u8c46\u8af8\u5cf6\u3068\u5c0f\u7b20\u539f\u8af8\u5cf6\u306b\u306f\u3001\u5f37\u98a8\u3001\u6ce2\u6d6a\u3001\u4e7e\u71e5\u3001\u971c\u306e\u6ce8\u610f\u5831\u3092\u767a\u8868\u4e2d\u3067\u3059\u3002","id":30716689779785729,"from_user_id":66578965,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://bit.ly/Fossetta&quot; rel=&quot;nofollow&quot;&gt;\u30d5\u30a9\u30bb\u30c3\u30bf ver.3.1.1&lt;/a&gt;"},{"from_user_id_str":"104041146","profile_image_url":"http://a2.twimg.com/profile_images/863965118/001jwatokyo_normal.jpg","created_at":"Thu, 27 Jan 2011 19:44:33 +0000","from_user":"jwa_tokyo","id_str":"30712787537756160","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6c17\u8c61\u5e81\u304b\u3089\u6771\u4eac\u306e\u5929\u6c17\u4e88\u5831\u304c\u767a\u8868\u3055\u308c\u307e\u3057\u305f\u3000\u305d\u306e\u5929\u6c17\u4e88\u5831\u3092\u3053\u3053\u3067\u3064\u3076\u3084\u3053\u3046\u304b\u306a\u3041\uff5e\uff1f\u3000\u8003\u3048\u4e2d\u3000\u307e\u3066\u6b21\u53f7\uff08\u7b11","id":30712787537756160,"from_user_id":104041146,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.docodemo.jp/twil/&quot; rel=&quot;nofollow&quot;&gt;Twil2 (Tweet Anytime, Anywhere by Mail)&lt;/a&gt;"},{"from_user_id_str":"144500192","profile_image_url":"http://a3.twimg.com/a/1294874399/images/default_profile_3_normal.png","created_at":"Thu, 27 Jan 2011 18:59:33 +0000","from_user":"kyo4to4","id_str":"30701462774358016","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6771\u4eac\u90fd \u516b\u4e08\u5cf6 - \u4eca\u65e5\u306e\u5929\u6c17\u306f\u30fb\u30fb\u30fb\u66c7\u306e\u3061\u6674\u3067\u3059\u306e\uff01","id":30701462774358016,"from_user_id":144500192,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/kyo4to4&quot; rel=&quot;nofollow&quot;&gt;lost-sheep-bot&lt;/a&gt;"},{"from_user_id_str":"165724582","profile_image_url":"http://a0.twimg.com/profile_images/1222842347/163087_1256281064506_1753997852_484926_2761052_n_normal.jpg","created_at":"Thu, 27 Jan 2011 17:39:06 +0000","from_user":"Rifqi_19931020","id_str":"30681213748387840","metadata":{"result_type":"recent"},"to_user_id":113796067,"text":"@CHLionRagbaby \u30b1\u30f3\u3061\u3083\u3093\u3001\u671d\u4eca\u307e\u3067\u306e\u81ea\u5206\u306e\u30db\u30fc\u30e0\u30a8\u30ea\u30a2\u304b\u3089\u307e\u3060\u975e\u5e38\u306b\u5bd2\u3044\u3068\u96e8\u304c\u964d\u3063\u3066\u3044\u305f..\u3069\u306e\u3088\u3046\u306b\u73fe\u5728\u306e\u6771\u4eac\u306e\u5929\u6c17\uff1f","id":30681213748387840,"from_user_id":165724582,"to_user":"CHLionRagbaby","geo":null,"iso_language_code":"ja","to_user_id_str":"113796067","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"120911929","profile_image_url":"http://a0.twimg.com/profile_images/772435076/20100312___capture2_normal.png","created_at":"Thu, 27 Jan 2011 17:28:30 +0000","from_user":"Cirno_fan","id_str":"30678546020040705","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u4eca\u65e5\u306e\u6771\u4eac\u306e\u5929\u6c17\u306f\u3001\u66c7\u6642\u3005\u6674\u3067\u6700\u9ad8\u6c17\u6e29\u306f8\u2103\uff01 \u6700\u4f4e\u6c17\u6e29\u306f2\u2103\u3060\u3063\u305f\u3088\uff01 RT @mimi22999 \uff08\uff65\u2200\uff65\uff09\uff4c\u3001\u865a\u5f31\u306a\u8005\u306b\u3068\u3063\u3066\u3001\u6717\u3089\u304b\u306a\u9854\u306f\u4e0a\u5929\u6c17\u3068\u540c\u3058\u304f\u3089\u3044\u3046\u308c\u3057\u3044\u3082\u306e\u3060\u3002\u30d5\u30e9\u30f3\u30af\u30ea\u30f3 #tenki #bot","id":30678546020040705,"from_user_id":120911929,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://blog.livedoor.jp/fairycirno/archives/34686.html&quot; rel=&quot;nofollow&quot;&gt;\u5e7b\u60f3\u90f7 \u9727\u306e\u6e56&lt;/a&gt;"},{"from_user_id_str":"65976527","profile_image_url":"http://a0.twimg.com/profile_images/452810990/little_italies_____normal.jpg","created_at":"Thu, 27 Jan 2011 17:00:03 +0000","from_user":"heta_weather01","id_str":"30671388754845696","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3010\u30d8\u30bf\u5929\u3011Tere hommikust.\u30a8\u30b9\u30c8\u30cb\u30a2\u3067\u3059\u3002\u4eca\u65e5\u306e\u6771\u4eac\u306e\u5929\u6c17\u306f\u6674\u6642\u3005\u66c7\u3067\u660e\u65e5\u306f\u66c7\u308a\u3067\u3059\u3002\u3061\u306a\u307f\u306b\u50d5\u306e\u3068\u3053\u308d\u3067\u306f\u66c7\u308a\u3067\u6c17\u6e29-7\u2103\u3067\u3059\u3002\u3061\u306a\u307f\u306b\u30b9\u30ab\u30a4\u30d7\u306e\u958b\u767a\u672c\u90e8\u306f\u50d5\u306e\u5bb6\u306b\u3042\u308b\u3093\u3067\u3059\u3002","id":30671388754845696,"from_user_id":65976527,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www15.atpages.jp/~kageyanma/&quot; rel=&quot;nofollow&quot;&gt;\u5730\u7403\u306e\u4e2d&lt;/a&gt;"},{"from_user_id_str":"7278433","profile_image_url":"http://a3.twimg.com/profile_images/1218965303/michael-1_normal.png","created_at":"Thu, 27 Jan 2011 16:50:16 +0000","from_user":"shimohiko","id_str":"30668926035689472","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3042\u308a\u3002\n\u3010\u7f8e\u4eba\u5929\u6c17/\u6771\u4eac\u3011\u7f8e\u4eba\u5929\u6c17\u30ad\u30e3\u30b9\u30bf\u30fc\u306e&quot;\u3055\u3042\u3084\u3093\u3055\u3093&quot;\u306b\u3088\u308b\u3068\u300c1/29(\u571f)\u306f\u304f\u3082\u308a\u3067\u3001\u964d\u6c34\u78ba\u738740%\u3001\u6700\u9ad8\u6c17\u6e29\u306f6\u2103\u3067\u6700\u4f4e\u6c17\u6e29\u306f1\u2103\u3067\u3059\u300d\u7f8e\u4eba\u5929\u6c17\u21d2http://bit.ly/djB8th http://twitpic.com/3twnl0 #bt_tenki","id":30668926035689472,"from_user_id":7278433,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://bijintenki.jp&quot; rel=&quot;nofollow&quot;&gt;bijintenki.jp&lt;/a&gt;"},{"from_user_id_str":"61770","profile_image_url":"http://a3.twimg.com/profile_images/1206955079/tw172a_normal.png","created_at":"Thu, 27 Jan 2011 16:40:52 +0000","from_user":"rsky","id_str":"30666561853333504","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3075\u3068\u591c\u7a7a\u3092\u898b\u4e0a\u3052\u3066\u30aa\u30ea\u30aa\u30f3\u304c\u898b\u3048\u306a\u304f\u3066\u300c\u6771\u4eac\u306b\u306f\u7a7a\u304c\u306a\u3044\u300d\u3068\u667a\u6075\u5b50\u306e\u3088\u3046\u306a\u3053\u3068\u3092\u601d\u3063\u305f\u308f\u3051\u3060\u304c\u3001\u305f\u3076\u3093\u534a\u5206\u3050\u3089\u3044\u306f\u5929\u6c17\u306e\u305b\u3044\u3067\u3059","id":30666561853333504,"from_user_id":61770,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;"},{"from_user_id_str":"103259265","profile_image_url":"http://a0.twimg.com/profile_images/767857973/anime_icon_normal.gif","created_at":"Thu, 27 Jan 2011 16:37:42 +0000","from_user":"liveshowonly","id_str":"30665765698928640","metadata":{"result_type":"recent"},"to_user_id":null,"text":"iPhone\u5929\u6c17\u3002\u6771\u4eac4\u2103\u3063\u3066\u7d50\u69cb\u5bd2\u304f\u306a\u3044\u3058\u3083\u3093\u3002\uff08\u78ba\u304b\u306b\u3082\u306e\u51c4\u304f\u5bd2\u3044\u8a33\u3067\u306f\u7121\u3044\uff09\u3002\u798f\u5ca1\u5e02\u306f1\u2103\u3060\u3002\u52dd\u3061\u3060\u305c\u3002Rock'n'roll","id":30665765698928640,"from_user_id":103259265,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"165242761","profile_image_url":"http://a1.twimg.com/profile_images/1147334744/SN3E00600001_normal.jpg","created_at":"Thu, 27 Jan 2011 16:37:35 +0000","from_user":"deuxavril0502","id_str":"30665736556912640","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6b8b\u5ff5\u3001\u79c1\u306f\u6c96\u7e04\u3067\u3059(&gt;_&lt;)\u3067\u3082\u304a\u5929\u6c17\u826f\u3055\u305d\u3046\u3067\u826f\u304b\u3063\u305f\u306d\u3002\u3053\u3061\u3089\u306f\u96e8\u3088\uff5eRT @rinandy2010: \u305d\u3046\u3067\u3059\u3063\u266a\u51fa\u5f35\u3067(^^)\u304a\u306d\u3048\u3055\u307e\u306f\u6771\u4eac\u3067\u3059\u304b\uff1f\u3081\u3063\u3061\u3083\u5929\u6c17\u826f\u304f\u3066\u3073\u3063\u304f\u308a\u3067\u3059\u3002\u624b\u888b\u3068\u304b\u5168\u7136\u3044\u3089\u306a\u3044\u3067\u3059\u306d\u30fc RT @deuxavril0502 \u6771\u4eac\u306a\u306e\u30fc\uff1f","id":30665736556912640,"from_user_id":165242761,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.echofon.com/&quot; rel=&quot;nofollow&quot;&gt;Echofon&lt;/a&gt;"},{"from_user_id_str":"163900288","profile_image_url":"http://a1.twimg.com/profile_images/1210224823/icon12945064955238_normal.jpg","created_at":"Thu, 27 Jan 2011 16:24:37 +0000","from_user":"nyao_yurichan","id_str":"30662472734089216","metadata":{"result_type":"recent"},"to_user_id":100985873,"text":"@ray_ko302 \u767a\u898b\u3042\u308a\u304c\u3068\u3067\u3059\u3045\u3002\u3053\u3061\u3089\u3067\u3082\u3088\u308d\u3057\u304f\u306d\u3002\u6771\u4eac\u306f\u4eca\u65e5\u3082\u4e7e\u71e5\u3067\u5927\u5909\u3088\u3002\u305d\u3063\u3061\u3068\u771f\u9006\u306e\u5929\u6c17\u3060\u306d\u3002\u30a2\u30a4\u30b9\u30d0\u30fc\u30f3\u304d\u3092\u3064\u3051\u3066\u3088\u306d\u3002","id":30662472734089216,"from_user_id":163900288,"to_user":"ray_ko302","geo":null,"iso_language_code":"ja","to_user_id_str":"100985873","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"49227098","profile_image_url":"http://a3.twimg.com/profile_images/965007882/____normal.jpg","created_at":"Thu, 27 Jan 2011 15:52:26 +0000","from_user":"dosannko6","id_str":"30654371016478720","metadata":{"result_type":"recent"},"to_user_id":108570870,"text":"@peke_hajiP \u4ffa\u304c\u6771\u4eac \u6765\u3066\u6700\u521d\u306b\u9a5a\u3044\u305f\u306e\u304c\u3001\u5929\u6c17\u4e88\u5831\u3067\u82b1\u7c89\u60c5\u5831\u304c\u6d41\u308c\u308b\u3053\u3068\u3067\u3001\u6700\u521d\u306b\u8a66\u3057\u305f\u306e\u304c \u30b4\u30ad\u69d8 \u53ec\u559a\u306e\u5100\u5f0f\u3067\u3059\u304a\uff1f","id":30654371016478720,"from_user_id":49227098,"to_user":"peke_hajip","geo":null,"iso_language_code":"ja","to_user_id_str":"108570870","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"71148934","profile_image_url":"http://a0.twimg.com/profile_images/1209223630/image_normal.jpg","created_at":"Thu, 27 Jan 2011 15:46:49 +0000","from_user":"123keiko","id_str":"30652958311981058","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u9727\u5cf6\u306e\u964d\u7070\u306e\u30cb\u30e5\u30fc\u30b9\u3092\u898b\u3066\u601d\u3044\u51fa\u3057\u307e\u3057\u305f\u3002\u4e0a\u4eac\u3057\u305f\u59cb\u3081\u306e\u9803\u3001\u6771\u4eac\u306f1\u5e74\u4e2d\u3001\u7070\u304c\u964d\u3089\u306a\u3044\u304b\u3089\u7a7a\u6c17\u304c\u6f84\u3093\u3067\u3066\u904e\u3054\u3057\u3084\u3059\u3044\u306a\u3041\u3001\u3068\u601d\u3063\u305f\u306a\u3041\u3001\u3063\u3066\u3002\n\u5b9f\u5bb6\u306f\u51ac\u306e\u5b63\u7bc0\u98a8\u3067\u685c\u5cf6\u306e\u7070\u304c\u964d\u308b\u5730\u533a\u3067\u3057\u305f\u3002\u9e7f\u5150\u5cf6\u306e\u5929\u6c17\u4e88\u5831\u3067\u306f\u3001\u6bce\u65e5\u3001\u685c\u5cf6\u4e0a\u7a7a\u306e\u98a8\u5411\u304d\u4e88\u5831\u304c\u3067\u307e\u3059\u3002\u3053\u308c\u304b\u3089\u306f\u9727\u5cf6\u3082\u304b\u306a\uff1f","id":30652958311981058,"from_user_id":71148934,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"91124773","profile_image_url":"http://a3.twimg.com/profile_images/1139308356/prof101007_3-1_normal.jpg","created_at":"Thu, 27 Jan 2011 15:43:35 +0000","from_user":"sanposuruhito","id_str":"30652146277945345","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u4eca\u5bb5\u306e\u90fd\u5fc3\u306f\u3001\u8eab\u3082\u5f15\u304d\u7de0\u307e\u308b\u3068\u3044\u3046\u3088\u308a\u306f\u51cd\u3048\u308b\u7a0b\u306e\u51b7\u6c17\u3092\u611f\u3058\u308b\u5bd2\u3044\u591c\u3067\u3059\u3002\u660e\u65e5\u65e5\u4e2d\u306e\u6771\u4eac\u306e\u5929\u6c17\u306f\u3001\u5915\u65b9\u306b\u591a\u5c11\u96f2\u304c\u51fa\u308b\u5834\u6240\u304c\u3042\u308a\u305d\u3046\u306a\u3082\u306e\u306e\u6982\u306d\u6674\u308c\u7a7a\u304c\u5e83\u304c\u308a\u7d9a\u3051\u305d\u3046\u3067\u3059\u3002\u6700\u9ad8\u6c17\u6e29\u306f\u30015-6\u5ea6\u4f4d\u3068\u306a\u308a\u305d\u3046\u3067\u3059\u3002#weather_tokyo","id":30652146277945345,"from_user_id":91124773,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://projects.playwell.jp/go/Saezuri&quot; rel=&quot;nofollow&quot;&gt;Saezuri&lt;/a&gt;"},{"from_user_id_str":"104712480","profile_image_url":"http://a3.twimg.com/profile_images/1204016928/icon5087697006727791368kuro2_normal.jpg","created_at":"Thu, 27 Jan 2011 15:31:51 +0000","from_user":"asongfor_xx","id_str":"30649192296751104","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u7f8e\u4eba\u5929\u6c17\u304b\u3089tweet\u3059\u308b\u3068\u3053\u3093\u306a\u611f\u3058\u306a\u3093\u3060\u306a\u3002 RT asongfor_xx: \u3010\u7f8e\u4eba\u5929\u6c17/\u6771\u4eac\u3011\u7f8e\u4eba\u5929\u6c17\u30ad\u30e3\u30b9\u30bf\u30fc\u306e&quot;\u304b\u306a\u3053\u3055\u3093&quot;\u306b\u3088\u308b\u3068\u300c1/28(\u91d1)\u306f\u6674\u6642\u3005\u304f\u3082\u308a\u3067\u3001\u964d\u6c34\u78ba\u73870%\u3001\u6700\u9ad8\u6c17\u6e29\u306f8\u2103\u3067\u6700\u4f4e\u6c17\u6e29\u306f1\u2103\u3067\u3059\u300d\u7f8e\u4eba\u5929\u6c17\u21d2http://bit.ly/djB8th","id":30649192296751104,"from_user_id":104712480,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twipple.jp/&quot; rel=&quot;nofollow&quot;&gt;\u3064\u3044\u3063\u3077\u308b for iPhone&lt;/a&gt;"},{"from_user_id_str":"165965113","profile_image_url":"http://a0.twimg.com/profile_images/1200885316/shogomeguro_normal.jpg","created_at":"Thu, 27 Jan 2011 15:24:35 +0000","from_user":"shogomeguro","id_str":"30647362477105152","metadata":{"result_type":"recent"},"to_user_id":null,"text":"2011.1.26 \u793e\u7a93\u304b\u3089\u3002\u4e0d\u601d\u8b70\u306a\u5929\u6c17\u3067\u3053\u306e\u5f8c\u96ea\u304c\u3061\u3089\u3064\u3044\u305f\u3093\u3060\u3088\u306d\u3002\u6771\u4eac\u306e\u521d\u96ea\u3067\u3057\u305f\u3002 http://instagr.am/p/BO_xn/","id":30647362477105152,"from_user_id":165965113,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://instagr.am&quot; rel=&quot;nofollow&quot;&gt;instagram&lt;/a&gt;"},{"from_user_id_str":"166018520","profile_image_url":"http://a0.twimg.com/profile_images/1207460953/100-40030_normal.jpg","created_at":"Thu, 27 Jan 2011 15:18:17 +0000","from_user":"115nayume","id_str":"30645776254238722","metadata":{"result_type":"recent"},"to_user_id":118384679,"text":"@dancinpea09 \u6771\u4eac\u3001\u660e\u65e5\u3082\u5929\u6c17\u826f\u3044\u307f\u305f\u3044\u3067\u3001\u3088\u304b\u3063\u305f\u3067\u3059\u306d\u30fc\u266a  \u30ca\u30a4\u30b9\u30c8\u30ea\u30c3\u30d7\uff01\u304a\u3084\u3059\u307f\u306a\u3055\u3044\u3002 http://twitpic.com/3tvun3","id":30645776254238722,"from_user_id":166018520,"to_user":"dancinpea09","geo":null,"iso_language_code":"ja","to_user_id_str":"118384679","source":"&lt;a href=&quot;http://tweetli.st/&quot; rel=&quot;nofollow&quot;&gt;TweetList!&lt;/a&gt;"},{"from_user_id_str":"202937674","profile_image_url":"http://a1.twimg.com/profile_images/1218797578/______2_normal.jpg","created_at":"Thu, 27 Jan 2011 15:11:48 +0000","from_user":"ryuusisan","id_str":"30644146498699264","metadata":{"result_type":"recent"},"to_user_id":203058384,"text":"@manaasutakshi \u6771\u4eac\u306f\u964d\u3063\u3066\u306a\u3044\u3093\u3067\u3059\u304b\u30fb\u30fb\u30fb\u5929\u6c17\u4e88\u5831\u306b\u3088\u308b\u3068\u660e\u65e5\u3082\u6771\u4eac\u306f\u964d\u3089\u306a\u3044\u305d\u3046\u3067\u3059\u306d\uff01\uff01\u79c1\u306e\u4f4f\u3093\u3067\u3044\u308b\u3068\u3053\u308d\u306f\u5fae\u5999\u306b\u964d\u308a\u305d\u3046\u3067\u51fa\u52e4\u6642\u306b\u964d\u3063\u3066\u305f\u3089\u3061\u3087\u3063\u3068\u5acc\u3060\u306a\u3041\u3000\u306a\u3093\u3066\u601d\u3044\u307e\u3059","id":30644146498699264,"from_user_id":202937674,"to_user":"manaasutakshi","geo":null,"iso_language_code":"ja","to_user_id_str":"203058384","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"3222182","profile_image_url":"http://a0.twimg.com/profile_images/1199090957/ProfilePhoto_normal.png","created_at":"Thu, 27 Jan 2011 15:09:35 +0000","from_user":"44mune","id_str":"30643589545459713","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3010\u7f8e\u4eba\u5929\u6c17/\u6771\u4eac\u3011\u7f8e\u4eba\u5929\u6c17\u30ad\u30e3\u30b9\u30bf\u30fc\u306e&quot;\u306e\u308a\u3055\u3093&quot;\u306b\u3088\u308b\u3068\u300c1/28(\u91d1)\u306f\u304f\u3082\u308a\u306e\u3061\u6674\u3067\u3001\u964d\u6c34\u78ba\u738710%\u3001\u6700\u9ad8\u6c17\u6e29\u306f9\u2103\u3067\u6700\u4f4e\u6c17\u6e29\u306f2\u2103\u3067\u3059\u300d http://twitpic.com/3tvruh","id":30643589545459713,"from_user_id":3222182,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://bijintenki.jp&quot; rel=&quot;nofollow&quot;&gt;bijintenki.jp&lt;/a&gt;"},{"from_user_id_str":"154341982","profile_image_url":"http://a0.twimg.com/profile_images/1155496864/_____normal.jpg","created_at":"Thu, 27 Jan 2011 15:02:39 +0000","from_user":"slave_420_bot","id_str":"30641845490946049","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u4eca\u30f3\u3068\u3053\u899a\u3048\u3066\u308b\u306e\u306f\u300c\u672d\u5e4c\u300d\u300c\u4ed9\u53f0\u300d\u300c\u6771\u4eac\u300d\u300c\u5927\u962a\u300d\u300c\u540d\u53e4\u5c4b\u300d\u300c\u798f\u5ca1\u300d\u306e\u4eca\u65e5\u3068\u660e\u65e5\u306e\u5929\u6c17\u3060\u306a\u3002\u5929\u6c17\u304c\u77e5\u308a\u305f\u3044\u6642\u306f\u300c\u5834\u6240\u300d\u3068\u300c\u4eca\u65e5\u306e\u5929\u6c17\u300d\u304b\u300c\u660e\u65e5\u306e\u5929\u6c17\u300d\u3092\u304a\u7533\u3057\u4ed8\u3051\u304f\u3060\u3055\u3044\u3001\u304a\u5b22\u69d8\uff1f","id":30641845490946049,"from_user_id":154341982,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://bot.syoyu.net/&quot; rel=&quot;nofollow&quot;&gt;\u7adc\u30f6\u5cf0\u90b8\u306e\u3069\u3053\u304b&lt;/a&gt;"},{"from_user_id_str":"72042678","profile_image_url":"http://a3.twimg.com/profile_images/1198771027/larxene_normal.jpg","created_at":"Thu, 27 Jan 2011 15:01:57 +0000","from_user":"larxene_bot","id_str":"30641668772339712","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u4eca\u65e5\u306e\u5929\u6c17\u306f \u672d\u5e4c\u304c\u6674\u308c \u4ed9\u53f0\u304c\u66c7\u6642\u3005\u96ea \u6771\u4eac\u304c\u6674\u306e\u3061\u66c7 \u540d\u53e4\u5c4b\u304c\u6674\u6642\u3005\u66c7 \u5927\u962a\u304c\u6674\u6642\u3005\u66c7 \u798f\u5ca1\u304c\u6674\u308c \u3089\u3057\u3044\u308f\u3088 \u6ce8\u610f\u3057\u306a\u3055\u3044","id":30641668772339712,"from_user_id":72042678,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www4.kiwi-us.com/~yuuna/larxene_bot_manual/index.html&quot; rel=&quot;nofollow&quot;&gt;\u5fd8\u5374\u306e\u57ce \u5730\u4e0a\u306e12\u968e&lt;/a&gt;"},{"from_user_id_str":"101331893","profile_image_url":"http://a1.twimg.com/sticky/default_profile_images/default_profile_0_normal.png","created_at":"Thu, 27 Jan 2011 15:01:34 +0000","from_user":"yoshiftan","id_str":"30641572240424961","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6771\u4eac\u306e\u5929\u6c17\u4e88\u5831\u3092\u898b\u3066\u3073\u3063\u304f\u308a\uff01\u6691\u305d\u3046\u3060\u306a\u3053\u308a\u3083\u3002","id":30641572240424961,"from_user_id":101331893,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"Keitai Mail"},{"from_user_id_str":"65207550","profile_image_url":"http://a3.twimg.com/profile_images/448731969/_________R_normal.jpg","created_at":"Thu, 27 Jan 2011 15:01:33 +0000","from_user":"kabuyo","id_str":"30641566469066752","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u5929\u6c17\u4e88\u5831\u3067\u306f\u6771\u4eac\u3067\u306f\u4e7e\u71e5\u6ce8\u610f\u5831\u304c\u7d9a\u3044\u3066\u3044\u308b\u3089\u3057\u3044\u306e\u306b\u3001\u306a\u305c\u79c1\u304c\u5e72\u3057\u305f\u6d17\u6fef\u7269\u306f\u96e8\u306b\u6fe1\u308c\u3066\u3057\u307e\u3063\u305f\u306e\u3060\u308d\u3046\u304b\u2025\u3000\u3088\u3063\u307d\u3069\u904b\u304c\u60aa\u3044\u3089\u3057\u3044\u2025","id":30641566469066752,"from_user_id":65207550,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"80309352","profile_image_url":"http://a1.twimg.com/profile_images/1223620418/image_normal.jpg","created_at":"Thu, 27 Jan 2011 14:54:30 +0000","from_user":"mikutyan","id_str":"30639792026812416","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6771\u4eac\u306e\u660e\u65e5\u306e\u5929\u6c17\u6559\u3048\u3066","id":30639792026812416,"from_user_id":80309352,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"80309352","profile_image_url":"http://a1.twimg.com/profile_images/1223620418/image_normal.jpg","created_at":"Thu, 27 Jan 2011 14:53:06 +0000","from_user":"mikutyan","id_str":"30639439847890944","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u660e\u65e5\u306e\u6771\u4eac\u306e\u5929\u6c17\u306a\u3093\u3060\u308d\u3046 \u96e8\u964d\u3063\u305f\u3089\u56f0\u308b\u3002","id":30639439847890944,"from_user_id":80309352,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"120911929","profile_image_url":"http://a0.twimg.com/profile_images/772435076/20100312___capture2_normal.png","created_at":"Thu, 27 Jan 2011 14:47:19 +0000","from_user":"Cirno_fan","id_str":"30637984684449792","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3042\u305f\u3044\u30c1\u30eb\u30ce\uff01\u30dc\u30c3\u30c8\u3060\u3051\u3069\u5929\u6c17\u4e88\u5831\u3082\u3067\u304d\u308b\u3088\uff01\u300c\u6771\u4eac\u306e\u4eca\u65e5\u306e\u5929\u6c17\u6559\u3048\u3066\u300d\u307f\u305f\u3044\u306bTL\u767a\u8a00\u3057\u3066\u304f\u308c\u308c\u3070\u8abf\u3079\u3066\u3042\u3052\u308b\uff01 #followme #followmejp #followmevip #tenki","id":30637984684449792,"from_user_id":120911929,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://blog.livedoor.jp/fairycirno/archives/34686.html&quot; rel=&quot;nofollow&quot;&gt;\u5e7b\u60f3\u90f7 \u9727\u306e\u6e56&lt;/a&gt;"},{"from_user_id_str":"33858777","profile_image_url":"http://a0.twimg.com/profile_images/352864695/muga_normal.jpg","created_at":"Thu, 27 Jan 2011 14:46:12 +0000","from_user":"collonist","id_str":"30637702105796610","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6771\u4eac\u3063\u3066\u51ac\u306f\u57fa\u672c\u7684\u306b\u6674\u308c\u3066\u308b\u304b\u3089\u3001\u5929\u6c17\u4e88\u5831\u898b\u306a\u304f\u3066\u3082\u5168\u7136\u554f\u984c\u306a\u3044\u3002\u5c71\u5f62\u3060\u3068\u660e\u65e5\u306f\u3069\u3093\u3060\u3051\u96ea\u304c\u964d\u308b\u306e\u304b\u3001\u3069\u3093\u3060\u3051\u5bd2\u3044\u306e\u304b\u628a\u63e1\u3057\u3066\u3068\u304d\u305f\u3044\u306e\u3067\u3001\u5929\u6c17\u4e88\u5831\u306f\u5fc5\u305a\u30c1\u30a7\u30c3\u30af\u3057\u3066\u305f\u306e\u3060\u304c\u3002","id":30637702105796610,"from_user_id":33858777,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;"},{"from_user_id_str":"79336228","profile_image_url":"http://a0.twimg.com/profile_images/1130029876/zmanmu_normal.png","created_at":"Thu, 27 Jan 2011 14:30:23 +0000","from_user":"atsuki777","id_str":"30633725041573890","metadata":{"result_type":"recent"},"to_user_id":null,"text":"MXTV\u306e\u5929\u6c17\u4e88\u5831\u521d\u3081\u3066\u898b\u305f\u3051\u3069\u6771\u4eac\u753a\u7530\u516b\u738b\u5b50\u3063\u3066\u5730\u57df\u8868\u793a\u304c\u2026w\u516b\u738b\u5b50\u3068\u753a\u7530\u306f\u6771\u4eac\u3058\u3083\u306a\u3044\u306e\u304bwww","id":30633725041573890,"from_user_id":79336228,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.echofon.com/&quot; rel=&quot;nofollow&quot;&gt;Echofon&lt;/a&gt;"},{"from_user_id_str":"90576097","profile_image_url":"http://a0.twimg.com/profile_images/1203320254/nanami1_normal.png","created_at":"Thu, 27 Jan 2011 14:28:26 +0000","from_user":"dycroft","id_str":"30633234245099521","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6771\u4eac\u306e\u660e\u65e5\u306e\u304a\u5929\u6c17\u3067\u3059","id":30633234245099521,"from_user_id":90576097,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://sourceforge.jp/projects/tween/wiki/FrontPage&quot; rel=&quot;nofollow&quot;&gt;Tween&lt;/a&gt;"},{"from_user_id_str":"147215268","profile_image_url":"http://a2.twimg.com/profile_images/1198555202/ichika-7.4.0-3P_normal.gif","created_at":"Thu, 27 Jan 2011 14:28:23 +0000","from_user":"kiaran5032","id_str":"30633222211637248","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6771\u4eac\u306e\u660e\u65e5\u306e\u304a\u5929\u6c17\u3067\u3059\u3002\u6674\u308c\u6642\u3005\u304f\u3082\u308a #MX","id":30633222211637248,"from_user_id":147215268,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://sourceforge.jp/projects/tween/wiki/FrontPage&quot; rel=&quot;nofollow&quot;&gt;Tween&lt;/a&gt;"},{"from_user_id_str":"1741599","profile_image_url":"http://a0.twimg.com/profile_images/1205744888/950637_normal.jpg","created_at":"Thu, 27 Jan 2011 14:28:21 +0000","from_user":"dasaitama","id_str":"30633212535382016","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6771\u4eac\u306e\u3001\u660e\u65e5\u306e\u304a\u5929\u6c17\u3067\u3059","id":30633212535382016,"from_user_id":1741599,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://sourceforge.jp/projects/tween/wiki/FrontPage&quot; rel=&quot;nofollow&quot;&gt;Tween&lt;/a&gt;"},{"from_user_id_str":"34140879","profile_image_url":"http://a3.twimg.com/profile_images/1186413535/__6_normal.jpg","created_at":"Thu, 27 Jan 2011 14:28:17 +0000","from_user":"ryuji_chaos","id_str":"30633197016449024","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6771\u4eac\u306e\u660e\u65e5\u306e\u304a\u5929\u6c17\u3067\u3059\u3002","id":30633197016449024,"from_user_id":34140879,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"1400","profile_image_url":"http://a0.twimg.com/profile_images/1198556839/westerndog_20090104_320x320_normal.jpg","created_at":"Thu, 27 Jan 2011 14:28:16 +0000","from_user":"westerndog","id_str":"30633191232503808","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6771\u4eac\u306e\u660e\u65e5\u306e\u304a\u5929\u6c17\u3067\u3059","id":30633191232503808,"from_user_id":1400,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://sourceforge.jp/projects/tween/wiki/FrontPage&quot; rel=&quot;nofollow&quot;&gt;Tween&lt;/a&gt;"},{"from_user_id_str":"89239832","profile_image_url":"http://a3.twimg.com/profile_images/519522846/noriko_normal.jpg","created_at":"Thu, 27 Jan 2011 14:15:01 +0000","from_user":"Noriko_Fujimoto","id_str":"30629857234788352","metadata":{"result_type":"recent"},"to_user_id":115645219,"text":"@nossa430402 \u5bcc\u5c71\u306f\u3059\u3054\u3044\u96ea\u307f\u305f\u3044\u3067\u3059\u306d\uff5e\uff08\u5bd2\u3063\uff09\u3000\u6771\u4eac\u306f\u305d\u306e\u5206\u6bce\u65e5\u826f\u3044\u5929\u6c17\u3067\u3059\u3002\u5bd2\u3044\u3051\u3069\u30fb\u30fb\u30fb\u3002","id":30629857234788352,"from_user_id":89239832,"to_user":"nossa430402","geo":null,"iso_language_code":"ja","to_user_id_str":"115645219","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"107265539","profile_image_url":"http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png","created_at":"Thu, 27 Jan 2011 14:01:10 +0000","from_user":"kurumasuki330","id_str":"30626371826884608","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u4eca\u9031\u571f\u66dc\u65e5\u3001\u8eca\u3067\u6771\u4eac\u304b\u3089\u6771\u540d\u3001\u7c73\u539f\u7d4c\u7531\u3067\u91d1\u6ca2\u306e\u5b9f\u5bb6\u3078\u3002\u305d\u3057\u3066\u3001\u6708\u66dc\u65e5\u306b\u6771\u4eac\u3078\u5e30\u308b\u30b9\u30b1\u30b8\u30e5\u30fc\u30eb\u3002\u5929\u6c17\u4e88\u5831\u3060\u3068\u4eca\u9031\u672b\u3001\u5317\u9678\u5730\u65b9\u306f\u5927\u96ea\uff01\u5e30\u308c\u308b\u304b\u81ea\u4fe1\u304c\u306a\u304f\u306a\u3063\u3066\u304d\u305f\u3002\u307e\u305f\u3001\u305d\u306e\u6642\u5b9f\u6cc1\u4e2d\u7d99\u3057\u307e\u3059\u3002","id":30626371826884608,"from_user_id":107265539,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twtr.jp&quot; rel=&quot;nofollow&quot;&gt;Keitai Web&lt;/a&gt;"},{"from_user_id_str":"155313166","profile_image_url":"http://a3.twimg.com/profile_images/1167554721/73931_1408370184003_1674923704_920888_5686998_n_normal.jpg","created_at":"Thu, 27 Jan 2011 14:00:50 +0000","from_user":"takuya_aoki","id_str":"30626287127105536","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6771\u4eac\u306f\u3081\u3063\u3061\u3083\u3044\u3044\u5929\u6c17\u3067\u904e\u3054\u3057\u3084\u3059\u3044\u3051\u3069\u30c9\u30e9\u30a4\u30a2\u30a4\u306b\u306f\u3061\u3087\u3063\u3068\u304d\u3064\u3044\u306a\u30fb\u30fb\u30fb\u76ee\u85ac\u304c\u624b\u653e\u305b\u306a\u3044","id":30626287127105536,"from_user_id":155313166,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"179984398","profile_image_url":"http://a1.twimg.com/profile_images/1182382310/g4350_normal.jpg","created_at":"Thu, 27 Jan 2011 13:52:50 +0000","from_user":"aiilabo","id_str":"30624271902449664","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u30d6\u30ed\u30b0\u66f4\u65b0 \u25c6\u611f\u60c5\u3092\u307f\u3064\u3081\u308b\u65b9\u6cd5 - \u3053\u3053\u6570\u65e5\u3001\u6642\u6298\u96e8\u304c\u30d1\u30e9\u30d1\u30e9\u3000\u5fae\u5999\u306a\u5929\u6c17\u306e\u6771\u4eac\u304b\u3089\u3042\u308b\u304c\u307e\u307e\u3067\u3059\u3002 \n\n \u4eca\u65e5\u306f\u3001\u6628\u65e5\u306e\u300c\u30de\u30a4\u30ca\u30b9\u611f\u60c5\u306f\u60aa\u3067\u3059\u304b\uff1f\n.. \u27aa http://am6.jp/e3e9tS","id":30624271902449664,"from_user_id":179984398,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://feedtweet.am6.jp/&quot; rel=&quot;nofollow&quot;&gt;\u261e feedtweet.jp \u261c&lt;/a&gt;"},{"from_user_id_str":"185992391","profile_image_url":"http://a2.twimg.com/profile_images/1192695844/otenki_normal.png","created_at":"Thu, 27 Jan 2011 13:00:37 +0000","from_user":"otenki_luci_bot","id_str":"30611130938298368","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u767a\u58f2\u65e5\u304b\u2026\u2026\u541b\u305f\u3061\u306b\u3068\u3063\u3066\u306f\u591a\u5206\u300190\u65e5\u5f8c\u306e\u3053\u3068\u3060\u3002\u3055\u3066\u660e\u65e5\u306e\u6771\u4eac\u90fd\u306e\u5929\u6c17\u306f\u6674\u306e\u3061\u66c7\u3001\u6700\u9ad8\u6c17\u6e29\u306f8\u5ea6\u3060\u305d\u3046\u3060\u3002\u3055\u3059\u304c\u306b\u4f55\u304b\u7740\u305f\u65b9\u304c\u3044\u3044\u305e\u3001\u30a4\u30fc\u30ce\u30c3\u30af\u3002","id":30611130938298368,"from_user_id":185992391,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/otenki_luci_bot&quot; rel=&quot;nofollow&quot;&gt;\u304a\u3066\u3093\u304d\u308b\u3057\u3075\u3047\u308b&lt;/a&gt;"},{"from_user_id_str":"65976527","profile_image_url":"http://a0.twimg.com/profile_images/452810990/little_italies_____normal.jpg","created_at":"Thu, 27 Jan 2011 13:00:04 +0000","from_user":"heta_weather01","id_str":"30610995843964928","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3010\u30d8\u30bf\u5929\u3011Jo napot.\u30cf\u30f3\u30ac\u30ea\u30fc\u3067\u3059\u3002\u6771\u4eac\u306e\u5929\u6c17\u306f\u6674\u306e\u3061\u66c7\u3067\u660e\u65e5\u306f\u6674\u6642\u3005\u66c7\u3001\u79c1\u306e\u3068\u3053\u308d\u3067\u306f\u66c7\u308a\u3067-2\u2103\u3067\u3059\u3002\u3048\u3001\u3053\u306e\u30d5\u30e9\u30a4\u30d1\u30f3\u3067\u3059\u304b\uff1f\u2026\u3061\u3087\u3063\u3068\u500b\u4eba\u7684\u306b\u6bb4\u308a\u305f\u3044\u4eba\u304c\u3044\u308b\u3093\u3067\u3059\u266a","id":30610995843964928,"from_user_id":65976527,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www15.atpages.jp/~kageyanma/&quot; rel=&quot;nofollow&quot;&gt;\u5730\u7403\u306e\u4e2d&lt;/a&gt;"},{"from_user_id_str":"109740077","profile_image_url":"http://a0.twimg.com/profile_images/1212763349/1d3d3821-6b3a-449f-a636-05b95ce3d5d8_normal.png","created_at":"Thu, 27 Jan 2011 12:59:41 +0000","from_user":"snow_arai","id_str":"30610896141156354","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u6771\u4eac\u306f\u3001\u30ab\u30e9\u30ab\u30e9\u9023\u7d9a\u3000\uff13\u4f4d\u30bf\u30a4 - \u65e9\u8d77\u304d\u2606\u304a\u5929\u6c17\u2606ONAIR\u65e5\u8a18\uff1a\u65e5\u7d4c\u30a6\u30fc\u30de\u30f3\u30aa\u30f3\u30e9\u30a4\u30f3 http://j.mp/hxP3Di","id":30610896141156354,"from_user_id":109740077,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"53407951","profile_image_url":"http://a1.twimg.com/profile_images/397960795/airman_normal.jpg","created_at":"Thu, 27 Jan 2011 12:57:32 +0000","from_user":"Airman2009","id_str":"30610355541508096","metadata":{"result_type":"recent"},"to_user_id":null,"text":". @04mmy22 @qsk1 \u98a8\u3068\u96f2\u30fb\u30fb\u30fb\u98db\u884c\u6a5f\u306e\u5927\u5c0f\u554f\u308f\u305a\u3001\u3053\u308c\u3070\u304b\u308a\u306f\u3069\u3046\u3057\u3088\u3046\u3082\u7121\u3044\u3067\u3059\u3088\u306d\u3002 \u6700\u8fd1\u306e\u5357\u6771\u4eac\u306e\u5929\u6c17\u306f\u5909\u3067\u3059\u306d\u3002\u3002\u3002","id":30610355541508096,"from_user_id":53407951,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://sourceforge.jp/projects/tween/wiki/FrontPage&quot; rel=&quot;nofollow&quot;&gt;Tween&lt;/a&gt;"},{"from_user_id_str":"111826019","profile_image_url":"http://a1.twimg.com/profile_images/1121742732/DVC00052_normal.jpg","created_at":"Thu, 27 Jan 2011 12:51:12 +0000","from_user":"nijiks","id_str":"30608762859421696","metadata":{"result_type":"recent"},"to_user_id":100014282,"text":"@sanaeru  \u79c1\u3053\u3093\u306a\u5929\u6c17\u306e\u4e2d\uff64\u4e8c\u6cca\u4e09\u65e5\u3067\u660e\u65e5\u304b\u3089\u6771\u4eac\u884c\u304f\u3093\u3067\u3059\u3051\u3069\u2026\uff61\u65e5\u66dc\u65e5\u98db\u884c\u6a5f\u98db\u3076\u304b\u3057\u3089\u2026\uff1f","id":30608762859421696,"from_user_id":111826019,"to_user":"sanaeru","geo":null,"iso_language_code":"ja","to_user_id_str":"100014282","source":"&lt;a href=&quot;http://z.twipple.jp/&quot; rel=&quot;nofollow&quot;&gt;\u3064\u3044\u3063\u3077\u308b/twipple&lt;/a&gt;"},{"from_user_id_str":"164951488","profile_image_url":"http://a3.twimg.com/profile_images/799082230/twitter_normal.jpg","created_at":"Thu, 27 Jan 2011 12:47:49 +0000","from_user":"minorif_jp","id_str":"30607912309096448","metadata":{"result_type":"recent"},"to_user_id":105078670,"text":"@ecco_HIRADO \u5e73\u6238\u725b\u3063\u3066\u5143\u306f\u30aa\u30e9\u30f3\u30c0\u304b\u3089\u304d\u305f\u725b\u306a\u3093\u3060\u306d\u3002\u304a\u3044\u3057\u305d\u3046\u2026\u3002\u5e73\u6238\u306f\u3088\u304b\u6240\u305f\u3043\u3002\u3042\u305f\u3044\u3052\u3047\u3093\u5b9f\u5bb6\u3093\u65b9\u306f\u30c0\u30d6\u30eb\u706b\u5c71\u3084\u9ce5\u30a4\u30f3\u30d5\u30eb\u3067\u5927\u5909\u3084\u3063\u3069\u3002\u6771\u4eac\u3093\u826f\u304b\u3068\u3053\u306f\u5929\u6c17\u306e\u5fc3\u914d\u305b\u305a\u66ae\u3089\u305b\u308b\u3068\u3053\u3058\u3083\u3063\u3069\u3093\u3002","id":30607912309096448,"from_user_id":164951488,"to_user":"ecco_HIRADO","geo":null,"iso_language_code":"ja","to_user_id_str":"105078670","source":"&lt;a href=&quot;http://z.twipple.jp/&quot; rel=&quot;nofollow&quot;&gt;\u3064\u3044\u3063\u3077\u308b/twipple&lt;/a&gt;"},{"from_user_id_str":"10456803","profile_image_url":"http://a1.twimg.com/profile_images/145280215/vot_icon_normal.png","created_at":"Thu, 27 Jan 2011 12:43:37 +0000","from_user":"vot_","id_str":"30606853377363968","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3086\u3063\u304f\u308a\u5929\u6c17\u4e88\u5831\u3067\u3059\u3002\u6771\u4eac\u90fd,\u5927\u5cf6\u5730\u65b9\u300228\u65e5\uff08\u91d1\u66dc\u65e5\u306e\u5929\u6c17\u306f\u6674\u308c\u306e\u3061\u66c7\u308a\u3001\u6700\u9ad8\u6c17\u6e29\u306f7\u5ea6 \u6700\u4f4e\u6c17\u6e29\u306f1\u5ea6\u3067\u3057\u3087\u3046\u3002","id":30606853377363968,"from_user_id":10456803,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://voiceoftwitter.com&quot; rel=&quot;nofollow&quot;&gt;\uff56\uff4f\uff49\uff43\uff45 \uff4f\uff46 \uff54\uff57\uff49\uff54\uff54\uff45\uff52&lt;/a&gt;"},{"from_user_id_str":"23900716","profile_image_url":"http://a3.twimg.com/profile_images/1092646332/twitter_icon_normal.png","created_at":"Thu, 27 Jan 2011 12:38:00 +0000","from_user":"keiyama","id_str":"30605442639990784","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3010\u7f8e\u4eba\u5929\u6c17/\u6771\u4eac\u3011\u7f8e\u4eba\u5929\u6c17\u30ad\u30e3\u30b9\u30bf\u30fc\u306e&quot;\u30c8\u30e2\u30b3\u3055\u3093&quot;\u306b\u3088\u308b\u3068\u300c1/27(\u6728)\u306f\u304f\u3082\u308a\u306e\u3061\u6674\u3067\u3001\u964d\u6c34\u78ba\u738710%\u3001\u6700\u9ad8\u6c17\u6e29\u306f9\u2103\u3067\u6700\u4f4e\u6c17\u6e29\u306f2\u2103\u3067\u3059\u300d\u7f8e\u4eba\u5929\u6c17\u21d2http://bit.ly/djB8th http://twitpic.com/3tuekm #bt_tenki","id":30605442639990784,"from_user_id":23900716,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://bijintenki.jp&quot; rel=&quot;nofollow&quot;&gt;bijintenki.jp&lt;/a&gt;"},{"from_user_id_str":"32018516","profile_image_url":"http://a0.twimg.com/profile_images/1089472069/237_normal.jpg","created_at":"Thu, 27 Jan 2011 12:36:28 +0000","from_user":"DigJandG","id_str":"30605057460281344","metadata":{"result_type":"recent"},"to_user_id":196050661,"text":"@kosumoworld55 \u5929\u6c17\u4e88\u5831\u3092\u898b\u3066\u3044\u3064\u3082\u601d\u3046\u3093\u3060\u3002\u4f55\u3067\u6a2a\u6d5c\u3042\u305f\u308a\u306f\u3044\u3064\u3082\u597d\u3044\u5929\u6c17\u306a\u3093\u3060\u3002\u6771\u4eac\u3082\u3002\u3061\u306a\u307f\u306b\u516c\u9b5a\u306f\u3042\u3093\u307e\u308a\u597d\u304d\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002\u306a\u3093\u304b\u81ed\u304f\u3063\u3066\u3002\u3067\u3082\u3001\u7435\u7436\u6e56\u3067\u516c\u9b5a\u3063\u3066\u3042\u307e\u308a\u805e\u304b\u306a\u3044\u69d8\u306a\u30fb\u30fb\u30fb\u3002\u4ffa\u304c\u91e3\u308a\u3092\u3057\u306a\u3044\u304b\u3089\u304b\u306a\uff1f","id":30605057460281344,"from_user_id":32018516,"to_user":"kosumoworld55","geo":null,"iso_language_code":"ja","to_user_id_str":"196050661","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"90935348","profile_image_url":"http://a1.twimg.com/sticky/default_profile_images/default_profile_0_normal.png","created_at":"Thu, 27 Jan 2011 12:34:34 +0000","from_user":"sorakeiko","id_str":"30604575937396737","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u95a2\u6771\u5730\u65b9\u306e\u660e\u65e5\u306e\u5929\u6c17\u2015\u7d9a\u304d\u3002\u660e\u65e5\u671d\u6700\u4f4e\u6c17\u6e29\u30fb\u8ed2\u4e26\u307f\u6c37\u70b9\u4e0b\u30fb\u5b87\u90fd\u5bae\u3001\u6c34\u6238\u30de\u30a4\u30ca\u30b95\u5ea6\u30fb\u6771\u4eac\u3001\u6a2a\u6d5c1\u5ea6\u3002\u6700\u9ad8\u6c17\u6e29\u30fb\u4eca\u65e5\u3088\u308a3\u5ea6\u524d\u5f8c\u4f4e\u304f5\uff5e8\u5ea6\u30fb\u6771\u4eac\u3001\u5343\u84498\u5ea6\u3002\u5411\u3053\u3046\u4e00\u9031\u9593\u30fb\u4e7e\u71e5\u3057\u305f\u6674\u308c\u7d9a\u304f\u30fb\uff08\u571f\uff09\u4f0a\u8c46\u8af8\u5cf6\u306b\u51b7\u305f\u3044\u96e8\u3084\u96ea\u304c\u898b\u3089\u308c\u305d\u3046\u3060\u304c\u3001\u95a2\u6771\u306e\u4e7e\u3044\u305f\u72b6\u614b\u7d9a\u304f\u3002","id":30604575937396737,"from_user_id":90935348,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"6113557","profile_image_url":"http://a3.twimg.com/profile_images/1227080026/profile_red_herencia_normal.png","created_at":"Thu, 27 Jan 2011 12:33:57 +0000","from_user":"fmoto7","id_str":"30604423327653888","metadata":{"result_type":"recent"},"to_user_id":null,"text":"&quot;\u6771\u4eac\u6c34\u904b\u7528\u30bb\u30f3\u30bf\u30fc\u306f\u3001\u524d\u534a\u6226\u304c\u7d42\u308f\u3063\u305f\u3068\u3053\u308d\u3067\u4e00\u6c17\u306b\u6c34\u5727\u9ad8\u3081\u3001\u5f8c\u534a\u6226\u59cb\u307e\u308b\u3068\u6c34\u5727\u4e0b\u3052\u3001\u7d42\u308f\u308b\u3068\u307e\u305f\u3059\u3050\u4e0a\u3052\u308b\u3002\u30c6\u30ec\u30d3\u306e\u8996\u8074\u3001\u305d\u306e\u65e5\u306e\u5929\u6c17\u306a\u3069\u30e9\u30a4...&quot; http://tumblr.com/xjy1cwiwrm","id":30604423327653888,"from_user_id":6113557,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tumblr.com/&quot; rel=&quot;nofollow&quot;&gt;Tumblr&lt;/a&gt;"},{"from_user_id_str":"118092912","profile_image_url":"http://a3.twimg.com/profile_images/1225330653/____normal.PNG","created_at":"Thu, 27 Jan 2011 12:29:13 +0000","from_user":"saechuchun","id_str":"30603231042207744","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u3067\u3082\u5730\u9707\u306e\u8a71\u984c\u306e\u3068\u304d\u306e\u300c\u3082\u3057\u6771\u4eac\u3067\u76f4\u4e0b\u578b\u5730\u9707\u304c\u8d77\u304d\u305f\u3089\u300d\u3068\u304b\u5929\u6c17\u4e88\u5831\u306e\u3068\u304d\u306f\u897f\u3067\u96e8\u3060\u308d\u3046\u3068\u300c\u6771\u4eac\u3067\u306f\u301c\u65e5\u9593\u4e7e\u71e5\u3057\u3066\u307e\u3059\u306d\u30fc\u300d\u3068\u304b\u305d\u3046\u3044\u3046\u30ad\u30fc\u5c40\u306e\u95a2\u6771\u4e2d\u5fc3\u4e3b\u7fa9\u306f\u597d\u304d\u3067\u306f\u306a\u3044","id":30603231042207744,"from_user_id":118092912,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twtr.jp&quot; rel=&quot;nofollow&quot;&gt;Keitai Web&lt;/a&gt;"}],"max_id":30724239224995840,"since_id":28179956995457024,"refresh_url":"?since_id=30724239224995840&q=%E6%9D%B1%E4%BA%AC%E3%80%80%E5%A4%A9%E6%B0%97","next_page":"?page=2&max_id=30724239224995840&rpp=50&lang=ja&q=%E6%9D%B1%E4%BA%AC%E3%80%80%E5%A4%A9%E6%B0%97","results_per_page":50,"page":1,"completed_in":0.170202,"warning":"adjusted since_id to 28179956995457024 (), requested since_id was older than allowed -- since_id removed for pagination.","since_id_str":"28179956995457024","max_id_str":"30724239224995840","query":"%E6%9D%B1%E4%BA%AC%E3%80%80%E5%A4%A9%E6%B0%97"}
+ test/json-data/numbers.json view
@@ -0,0 +1,1 @@+[1.15, 1.3224999999999998, 1.5208749999999998, 1.7490062499999994, 2.0113571874999994, 2.313060765624999, 2.6600198804687487, 3.0590228625390607, 3.5178762919199196, 4.045557735707907, 4.652391396064092, 5.350250105473706, 6.152787621294761, 7.075705764488975, 8.137061629162321, 9.357620873536668, 10.761264004567169, 12.375453605252241, 14.231771646040077, 16.36653739294609, 18.821518001888, 21.644745702171196, 24.891457557496874, 28.625176191121405, 32.918952619789614, 37.856795512758055, 43.535314839671756, 50.06561206562252, 57.57545387546589, 66.21177195678577, 76.14353775030362, 87.56506841284916, 100.69982867477653, 115.804802975993, 133.17552342239193, 153.15185193575073, 176.1246297261133, 202.5433241850303, 232.9248228127848, 267.86354623470254, 308.04307816990786, 354.24953989539404, 407.3869708797031, 468.49501651165855, 538.7692689884072, 619.5846593366683, 712.5223582371685, 819.4007119727437, 942.3108187686552, 1083.6574415839534, 1246.2060578215462, 1433.136966494778, 1648.1075114689947, 1895.323638189344, 2179.622183917745, 2506.565511505407, 2882.5503382312177, 3314.9328889659, 3812.172822310785, 4383.998745657402, 5041.598557506012, 5797.838341131914, 6667.5140923017, 7667.641206146955, 8817.787387068996, 10140.455495129345, 11661.523819398746, 13410.752392308557, 15422.365251154839, 17735.720038828065, 20396.078044652273, 23455.489751350113, 26973.813214052625, 31019.885196160518, 35672.867975584595, 41023.79817192228, 47177.36789771062, 54253.97308236721, 62392.06904472228, 71750.87940143062, 82513.5113116452, 94890.53800839197, 109124.11870965076, 125492.73651609836, 144316.6469935131, 165964.14404254008, 190858.76564892105, 219487.5804962592, 252410.71757069806, 290272.32520630275, 333813.17398724816, 383885.1500853353, 441467.9225981356, 507688.1109878559, 583841.3276360342, 671417.5267814393, 772130.1557986551, 887949.6791684533, 1021142.1310437213, 1174313.4507002793, 1350460.4683053212, 1553029.5385511192, 1785983.969333787, 2053881.564733855, 2361963.7994439327, 2716258.369360523, 3123697.124764601, 3592251.6934792907, 4131089.447501184, 4750752.864626361, 5463365.794320315, 6282870.663468362, 7225301.262988616, 8309096.452436907, 9555460.920302443, 10988780.058347808, 12637097.067099977, 14532661.627164973, 16712560.871239718, 19219445.001925673, 22102361.752214525, 25417716.0150467, 29230373.417303704, 33614929.42989926, 38657168.84438414, 44455744.17104176, 51124105.79669802, 58792721.66620272, 67611629.91613312, 77753374.40355308, 89416380.56408603, 102828837.64869894, 118253163.29600377, 135991137.79040432, 156389808.45896497, 179848279.7278097, 206825521.68698114, 237849349.94002828, 273526752.4310325, 314555765.2956874, 361739130.09004045, 415999999.60354644, 478399999.5440784, 550159999.4756901, 632683999.3970436, 727586599.3066001, 836724589.20259, 962233277.5829784, 1106568269.2204251, 1272553509.6034887, 1463436536.044012, 1682952016.4506137, 1935394818.9182055, 2225704041.755936, 2559559648.019326, 2943493595.222225, 3385017634.5055585, 3892770279.681392, 4476685821.6336, 5148188694.87864, 5920416999.1104355, 6808479548.977001, 7829751481.32355, 9004214203.522081, 10354846334.050394, 11908073284.157951, 13694284276.781643, 15748426918.29889, 18110690956.04372, 20827294599.450275, 23951388789.367817, 27544097107.772987, 31675711673.938934, 36427068425.02977, 41891128688.78423, 48174797992.10186, 55401017690.91714, 63711170344.5547, 73267845896.2379, 84258022780.67358, 96896726197.77461, 111431235127.4408, 128145920396.5569, 147367808456.04044, 169472979724.44647, 194893926683.11343, 224128015685.58044, 257747218038.41748, 296409300744.1801, 340870695855.80707, 392001300234.1781, 450801495269.3048, 518421719559.70044, 596184977493.6555, 685612724117.7037, 788454632735.3593, 906722827645.6631, 1042731251792.5125, 1199140939561.3892, 1379012080495.5974, 1585863892569.937, 1823743476455.4275, 2097304997923.7415, 2411900747612.3022, 2773685859754.1475, 3189738738717.2695, 3668199549524.8594, 4218429481953.5884, 4851193904246.626, 5578872989883.619, 6415703938366.162, 7378059529121.086, 8484768458489.248, 9757483727262.635, 11221106286352.03, 12904272229304.832, 14839913063700.555, 17065900023255.637, 19625785026743.98, 22569652780755.58, 25955100697868.91, 29848365802549.246, 34325620672931.63, 39474463773871.375, 45395633339952.07, 52204978340944.88, 60035725092086.61, 69041083855899.59, 79397246434284.53, 91306833399427.2, 105002858409341.27, 120753287170742.45, 138866280246353.81, 159696222283306.88, 183650655625802.88, 211198253969673.3, 242877992065124.28, 279309690874892.9, 321206144506126.8, 369387066182045.8, 424795126109352.6, 488514395025755.5, 561791554279618.75, 646060287421561.5, 742969330534795.8, 854414730115015.0, 982576939632267.1, 1129963480577107.2, 1299458002663673.2, 1494376703063224.0, 1718533208522707.5, 1976313189801113.5, 2272760168271280.5, 2613674193511972.0, 3005725322538767.5, 3456584120919582.5, 3975071739057519.5, 4571332499916147.0, 5257032374903569.0, 6045587231139104.0, 6952425315809969.0, 7995289113181464.0, 9194582480158682.0, 1.0573769852182484e+16, 1.2159835330009856e+16, 1.3983810629511332e+16, 1.6081382223938032e+16, 1.8493589557528736e+16, 2.1267627991158044e+16, 2.4457772189831748e+16, 2.8126438018306508e+16, 3.234540372105248e+16, 3.719721427921035e+16, 4.27767964210919e+16, 4.919331588425568e+16, 5.657231326689403e+16, 6.505816025692813e+16, 7.481688429546734e+16, 8.603941693978744e+16, 9.894532948075555e+16, 1.1378712890286886e+17, 1.3085519823829918e+17, 1.5048347797404406e+17, 1.7305599967015066e+17, 1.9901439962067325e+17, 2.288665595637742e+17, 2.6319654349834032e+17, 3.026760250230913e+17, 3.48077428776555e+17, 4.002890430930382e+17, 4.603323995569939e+17, 5.29382259490543e+17, 6.087895984141244e+17, 7.00108038176243e+17, 8.051242439026793e+17, 9.258928804880812e+17, 1.0647768125612933e+18, 1.224493334445487e+18, 1.4081673346123103e+18, 1.6193924348041567e+18, 1.8623013000247798e+18, 2.1416464950284966e+18, 2.462893469282771e+18, 2.8323274896751867e+18, 3.257176613126464e+18, 3.7457531050954337e+18, 4.3076160708597484e+18, 4.95375848148871e+18, 5.696822253712016e+18, 6.551345591768818e+18, 7.53404743053414e+18, 8.66415454511426e+18, 9.963777726881399e+18, 1.1458344385913608e+19, 1.3177096043800648e+19, 1.5153660450370744e+19, 1.7426709517926355e+19, 2.0040715945615307e+19, 2.30468233374576e+19, 2.650384683807624e+19, 3.047942386378767e+19, 3.505133744335582e+19, 4.030903805985919e+19, 4.635539376883806e+19, 5.330870283416377e+19, 6.130500825928833e+19, 7.0500759498181575e+19, 8.10758734229088e+19, 9.323725443634512e+19, 1.0722284260179688e+20, 1.233062689920664e+20, 1.4180220934087634e+20, 1.6307254074200778e+20, 1.8753342185330894e+20, 2.1566343513130526e+20, 2.4801295040100103e+20, 2.8521489296115116e+20, 3.2799712690532385e+20, 3.7719669594112236e+20, 4.337762003322907e+20, 4.988426303821342e+20, 5.7366902493945437e+20, 6.597193786803724e+20, 7.586772854824282e+20, 8.724788783047925e+20, 1.0033507100505113e+21, 1.1538533165580878e+21, 1.326931314041801e+21, 1.5259710111480708e+21, 1.7548666628202813e+21, 2.0180966622433234e+21, 2.3208111615798217e+21, 2.668932835816795e+21, 3.0692727611893135e+21, 3.529663675367711e+21, 4.059113226672867e+21, 4.667980210673796e+21, 5.368177242274866e+21, 6.173403828616095e+21, 7.099414402908508e+21, 8.164326563344784e+21, 9.388975547846502e+21, 1.0797321880023475e+22, 1.2416920162026996e+22, 1.4279458186331043e+22, 1.64213769142807e+22, 1.8884583451422802e+22, 2.171727096913622e+22, 2.497486161450665e+22, 2.872109085668265e+22, 3.3029254485185042e+22, 3.798364265796279e+22, 4.368118905665721e+22, 5.0233367415155795e+22, 5.7768372527429155e+22, 6.643362840654352e+22, 7.639867266752504e+22, 8.78584735676538e+22, 1.0103724460280185e+23, 1.1619283129322213e+23, 1.3362175598720545e+23, 1.5366501938528623e+23, 1.7671477229307915e+23, 2.0322198813704103e+23, 2.3370528635759717e+23, 2.687610793112367e+23, 3.090752412079222e+23, 3.554365273891105e+23, 4.0875200649747705e+23, 4.700648074720986e+23, 5.405745285929133e+23, 6.216607078818502e+23, 7.149098140641278e+23, 8.221462861737468e+23, 9.454682290998088e+23, 1.0872884634647801e+24, 1.250381732984497e+24, 1.4379389929321714e+24, 1.653629841871997e+24, 1.9016743181527962e+24, 2.1869254658757156e+24, 2.5149642857570727e+24, 2.8922089286206334e+24, 3.326040267913728e+24, 3.824946308100787e+24, 4.3986882543159046e+24, 5.05849149246329e+24, 5.817265216332783e+24, 6.6898549987827e+24, 7.693333248600105e+24, 8.84733323589012e+24, 1.0174433221273637e+25, 1.1700598204464681e+25, 1.3455687935134384e+25, 1.547404112540454e+25, 1.779514729421522e+25, 2.04644193883475e+25, 2.353408229659962e+25, 2.7064194641089563e+25, 3.1123823837253e+25, 3.5792397412840944e+25, 4.116125702476708e+25, 4.733544557848214e+25, 5.443576241525446e+25, 6.260112677754262e+25, 7.199129579417401e+25, 8.27899901633001e+25, 9.52084886877951e+25, 1.0948976199096437e+26, 1.25913226289609e+26, 1.4480021023305035e+26, 1.665202417680079e+26, 1.9149827803320907e+26, 2.202230197381904e+26, 2.5325647269891895e+26, 2.9124494360375676e+26, 3.349316851443203e+26, 3.8517143791596826e+26, 4.429471536033635e+26, 5.09389226643868e+26, 5.857976106404481e+26, 6.736672522365153e+26, 7.747173400719924e+26, 8.909249410827912e+26, 1.0245636822452099e+27, 1.1782482345819913e+27, 1.35498546976929e+27, 1.5582332902346833e+27, 1.7919682837698857e+27, 2.0607635263353683e+27, 2.3698780552856732e+27, 2.725359763578524e+27, 3.1341637281153025e+27, 3.6042882873325974e+27, 4.1449315304324867e+27, 4.7666712599973594e+27, 5.481671948996963e+27, 6.303922741346507e+27, 7.249511152548482e+27, 8.336937825430755e+27, 9.587478499245366e+27, 1.102560027413217e+28, 1.2679440315251995e+28, 1.4581356362539794e+28, 1.6768559816920761e+28, 1.9283843789458875e+28, 2.2176420357877703e+28, 2.5502883411559357e+28, 2.932831592329326e+28, 3.3727563311787245e+28, 3.8786697808555327e+28, 4.460470247983863e+28, 5.129540785181441e+28, 5.8989719029586575e+28, 6.783817688402455e+28, 7.801390341662823e+28, 8.971598892912247e+28, 1.0317338726849081e+29, 1.1864939535876443e+29, 1.3644680466257909e+29, 1.5691382536196593e+29, 1.8045089916626083e+29, 2.0751853404119993e+29, 2.3864631414737988e+29, 2.7444326126948684e+29, 3.1560975045990984e+29, 3.629512130288963e+29, 4.173938949832307e+29, 4.800029792307153e+29, 5.520034261153225e+29, 6.348039400326208e+29, 7.300245310375139e+29, 8.39528210693141e+29, 9.65457442297112e+29, 1.1102760586416787e+30, 1.2768174674379305e+30, 1.46834008755362e+30, 1.6885911006866628e+30, 1.941879765789662e+30, 2.2331617306581113e+30, 2.568135990256828e+30, 2.9533563887953516e+30, 3.396359847114654e+30, 3.905813824181852e+30, 4.491685897809129e+30, 5.165438782480498e+30, 5.940254599852573e+30, 6.831292789830458e+30, 7.855986708305026e+30, 9.034384714550779e+30, 1.0389542421733396e+31, 1.1947973784993405e+31, 1.3740169852742412e+31, 1.5801195330653773e+31, 1.817137463025184e+31, 2.0897080824789613e+31, 2.4031642948508052e+31, 2.7636389390784257e+31, 3.1781847799401893e+31, 3.6549124969312175e+31, 4.2031493714709e+31, 4.833621777191535e+31, 5.558665043770264e+31, 6.392464800335803e+31, 7.3513345203861735e+31, 8.454034698444098e+31, 9.722139903210713e+31, 1.1180460888692319e+32, 1.2857530021996165e+32, 1.478615952529559e+32, 1.7004083454089925e+32, 1.9554695972203414e+32, 2.2487900368033926e+32, 2.586108542323901e+32, 2.974024823672486e+32, 3.420128547223359e+32, 3.933147829306862e+32, 4.523120003702891e+32, 5.2015880042583246e+32, 5.981826204897073e+32, 6.879100135631634e+32, 7.910965155976377e+32, 9.097609929372833e+32, 1.0462251418778757e+33, 1.2031589131595571e+33, 1.3836327501334904e+33, 1.591177662653514e+33, 1.8298543120515409e+33, 2.104332458859272e+33, 2.4199823276881623e+33, 2.7829796768413863e+33, 3.200426628367594e+33, 3.6804906226227334e+33, 4.232564216016143e+33, 4.867448848418564e+33, 5.597566175681348e+33, 6.437201102033549e+33, 7.402781267338582e+33, 8.513198457439368e+33, 9.790178226055273e+33, 1.1258704959963564e+34, 1.2947510703958096e+34, 1.488963730955181e+34, 1.712308290598458e+34, 1.9691545341882266e+34, 2.2645277143164603e+34, 2.604206871463929e+34, 2.994837902183518e+34, 3.4440635875110457e+34, 3.960673125637702e+34, 4.554774094483357e+34, 5.237990208655861e+34, 6.023688739954239e+34, 6.927242050947375e+34, 7.96632835858948e+34, 9.161277612377902e+34, 1.0535469254234585e+35, 1.2115789642369772e+35, 1.3933158088725236e+35, 1.6023131802034022e+35, 1.8426601572339122e+35, 2.119059180818999e+35, 2.4369180579418488e+35, 2.8024557666331258e+35, 3.2228241316280944e+35, 3.706247751372308e+35, 4.262184914078154e+35, 4.901512651189877e+35, 5.636739548868358e+35, 6.482250481198611e+35, 7.454588053378403e+35, 8.572776261385162e+35, 9.858692700592935e+35, 1.1337496605681875e+36, 1.3038121096534156e+36, 1.499383926101428e+36, 1.7242915150166418e+36, 1.982935242269138e+36, 2.2803755286095084e+36]
+ test/json-data/twitter1.json view
@@ -0,0 +1,1 @@+{"results":[{"from_user_id_str":"80430860","profile_image_url":"http://a2.twimg.com/profile_images/536455139/icon32_normal.png","created_at":"Wed, 26 Jan 2011 07:07:02 +0000","from_user":"kazu_yamamoto","id_str":"30159761706061824","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Haskell Server Pages \u3063\u3066\u3001\u307e\u3060\u7d9a\u3044\u3066\u3044\u305f\u306e\u304b\uff01","id":30159761706061824,"from_user_id":80430860,"geo":null,"iso_language_code":"no","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"}],"max_id":30159761706061824,"since_id":0,"refresh_url":"?since_id=30159761706061824&q=haskell","next_page":"?page=2&max_id=30159761706061824&rpp=1&q=haskell","results_per_page":1,"page":1,"completed_in":0.012606,"since_id_str":"0","max_id_str":"30159761706061824","query":"haskell"}
+ test/json-data/twitter10.json view
@@ -0,0 +1,1 @@+{"results":[{"from_user_id_str":"207858021","profile_image_url":"http://a3.twimg.com/sticky/default_profile_images/default_profile_2_normal.png","created_at":"Wed, 26 Jan 2011 04:30:38 +0000","from_user":"pboudarga","id_str":"30120402839666689","metadata":{"result_type":"recent"},"to_user_id":null,"text":"I'm at Rolla Sushi Grill (27737 Bouquet Canyon Road, #106, Btw Haskell Canyon and Rosedell Drive, Saugus) http://4sq.com/gqqdhs","id":30120402839666689,"from_user_id":207858021,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://foursquare.com&quot; rel=&quot;nofollow&quot;&gt;foursquare&lt;/a&gt;"},{"from_user_id_str":"69988683","profile_image_url":"http://a0.twimg.com/profile_images/1211955817/avatar_7888_normal.gif","created_at":"Wed, 26 Jan 2011 04:25:23 +0000","from_user":"YNK33","id_str":"30119083059978240","metadata":{"result_type":"recent"},"to_user_id":null,"text":"hsndfile 0.5.0: Free and open source Haskell bindings for libsndfile http://bit.ly/gHaBWG Mac Os","id":30119083059978240,"from_user_id":69988683,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"81492","profile_image_url":"http://a1.twimg.com/profile_images/423894208/Picture_7_normal.jpg","created_at":"Wed, 26 Jan 2011 04:24:28 +0000","from_user":"satzz","id_str":"30118851488251904","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Emacs\u306e\u30e2\u30fc\u30c9\u8868\u793a\u304c\u4eca(Ruby Controller Outputz RoR Flymake REl hs)\u3068\u306a\u3063\u3066\u3066\u3088\u304f\u308f\u304b\u3089\u306a\u3044\u3093\u3060\u3051\u3069\u6700\u5f8c\u306eREl\u3068\u304bhs\u3063\u3066\u4f55\u3060\u308d\u3046\u2026haskell\u3068\u304b2\u5e74\u4ee5\u4e0a\u66f8\u3044\u3066\u306a\u3044\u3051\u3069\u2026","id":30118851488251904,"from_user_id":81492,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;"},{"from_user_id_str":"9518356","profile_image_url":"http://a2.twimg.com/profile_images/119165723/ocaml-icon_normal.png","created_at":"Wed, 26 Jan 2011 04:19:19 +0000","from_user":"planet_ocaml","id_str":"30117557788741632","metadata":{"result_type":"recent"},"to_user_id":null,"text":"I so miss #haskell type classes in #ocaml - i want to do something like refinement. Also why does ocaml not have... http://bit.ly/geYRwt","id":30117557788741632,"from_user_id":9518356,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"218059","profile_image_url":"http://a1.twimg.com/profile_images/1053837723/twitter-icon9_normal.jpg","created_at":"Wed, 26 Jan 2011 04:16:32 +0000","from_user":"aprikip","id_str":"30116854940835840","metadata":{"result_type":"recent"},"to_user_id":null,"text":"yatex-mode\u3084haskell-mode\u306e\u3053\u3068\u3067\u3059\u306d\u3001\u308f\u304b\u308a\u307e\u3059\u3002","id":30116854940835840,"from_user_id":218059,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://sites.google.com/site/yorufukurou/&quot; rel=&quot;nofollow&quot;&gt;YoruFukurou&lt;/a&gt;"},{"from_user_id_str":"216363","profile_image_url":"http://a1.twimg.com/profile_images/72454310/Tim-Avatar_normal.png","created_at":"Wed, 26 Jan 2011 04:15:30 +0000","from_user":"dysinger","id_str":"30116594684264448","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Haskell in Hawaii tonight for me... #fun","id":30116594684264448,"from_user_id":216363,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.nambu.com/&quot; rel=&quot;nofollow&quot;&gt;Nambu&lt;/a&gt;"},{"from_user_id_str":"1774820","profile_image_url":"http://a2.twimg.com/profile_images/61169291/dan_desert_thumb_normal.jpg","created_at":"Wed, 26 Jan 2011 04:13:36 +0000","from_user":"DanMil","id_str":"30116117682851840","metadata":{"result_type":"recent"},"to_user_id":1594784,"text":"@ojrac @chewedwire @tomheon Haskell isn't a language, it's a belief system.  A seductive one...","id":30116117682851840,"from_user_id":1774820,"to_user":"ojrac","geo":null,"iso_language_code":"en","to_user_id_str":"1594784","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"659256","profile_image_url":"http://a0.twimg.com/profile_images/746976711/angular-final_normal.jpg","created_at":"Wed, 26 Jan 2011 04:11:06 +0000","from_user":"djspiewak","id_str":"30115488931520512","metadata":{"result_type":"recent"},"to_user_id":null,"text":"One of the very nice things about Haskell as opposed to SML is the reduced proliferation of identifiers (e.g. andb, orb, etc). #typeclasses","id":30115488931520512,"from_user_id":659256,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://itunes.apple.com/us/app/twitter/id409789998?mt=12&quot; rel=&quot;nofollow&quot;&gt;Twitter for Mac&lt;/a&gt;"},{"from_user_id_str":"144546280","profile_image_url":"http://a1.twimg.com/a/1295051201/images/default_profile_1_normal.png","created_at":"Wed, 26 Jan 2011 04:06:12 +0000","from_user":"listwarenet","id_str":"30114255890026496","metadata":{"result_type":"recent"},"to_user_id":null,"text":"http://www.listware.net/201101/haskell-cafe/84752-re-haskell-cafe-gpl-license-of-h-matrix-and-prelude-numeric.html Re: Haskell-c","id":30114255890026496,"from_user_id":144546280,"geo":null,"iso_language_code":"no","to_user_id_str":null,"source":"&lt;a href=&quot;http://1e10.org/cloud/&quot; rel=&quot;nofollow&quot;&gt;1e10&lt;/a&gt;"},{"from_user_id_str":"1594784","profile_image_url":"http://a2.twimg.com/profile_images/378515773/square-profile_normal.jpg","created_at":"Wed, 26 Jan 2011 04:01:29 +0000","from_user":"ojrac","id_str":"30113067333324800","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @tomheon: @ojrac @chewedwire Don't worry, learning Haskell will not give you any clear idea what monad means.","id":30113067333324800,"from_user_id":1594784,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"}],"max_id":30120402839666689,"since_id":0,"refresh_url":"?since_id=30120402839666689&q=haskell","next_page":"?page=2&max_id=30120402839666689&rpp=10&q=haskell","results_per_page":10,"page":1,"completed_in":0.012714,"since_id_str":"0","max_id_str":"30120402839666689","query":"haskell"}
+ test/json-data/twitter100.json view
@@ -0,0 +1,1 @@+{"results":[{"from_user_id_str":"3646730","profile_image_url":"http://a3.twimg.com/profile_images/404973767/avatar_normal.jpg","created_at":"Wed, 26 Jan 2011 04:35:07 +0000","from_user":"nicolaslara","id_str":"30121530767708160","metadata":{"result_type":"recent"},"to_user_id":18616016,"text":"@josej30 Python y Clojure. Obviamente son diferentes, y cada uno tiene sus ventajas y desventajas. De Haskell faltar\u00eda pattern matching","id":30121530767708160,"from_user_id":3646730,"to_user":"josej30","geo":null,"iso_language_code":"es","to_user_id_str":"18616016","source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"207858021","profile_image_url":"http://a3.twimg.com/sticky/default_profile_images/default_profile_2_normal.png","created_at":"Wed, 26 Jan 2011 04:30:38 +0000","from_user":"pboudarga","id_str":"30120402839666689","metadata":{"result_type":"recent"},"to_user_id":null,"text":"I'm at Rolla Sushi Grill (27737 Bouquet Canyon Road, #106, Btw Haskell Canyon and Rosedell Drive, Saugus) http://4sq.com/gqqdhs","id":30120402839666689,"from_user_id":207858021,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://foursquare.com&quot; rel=&quot;nofollow&quot;&gt;foursquare&lt;/a&gt;"},{"from_user_id_str":"69988683","profile_image_url":"http://a0.twimg.com/profile_images/1211955817/avatar_7888_normal.gif","created_at":"Wed, 26 Jan 2011 04:25:23 +0000","from_user":"YNK33","id_str":"30119083059978240","metadata":{"result_type":"recent"},"to_user_id":null,"text":"hsndfile 0.5.0: Free and open source Haskell bindings for libsndfile http://bit.ly/gHaBWG Mac Os","id":30119083059978240,"from_user_id":69988683,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"81492","profile_image_url":"http://a1.twimg.com/profile_images/423894208/Picture_7_normal.jpg","created_at":"Wed, 26 Jan 2011 04:24:28 +0000","from_user":"satzz","id_str":"30118851488251904","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Emacs\u306e\u30e2\u30fc\u30c9\u8868\u793a\u304c\u4eca(Ruby Controller Outputz RoR Flymake REl hs)\u3068\u306a\u3063\u3066\u3066\u3088\u304f\u308f\u304b\u3089\u306a\u3044\u3093\u3060\u3051\u3069\u6700\u5f8c\u306eREl\u3068\u304bhs\u3063\u3066\u4f55\u3060\u308d\u3046\u2026haskell\u3068\u304b2\u5e74\u4ee5\u4e0a\u66f8\u3044\u3066\u306a\u3044\u3051\u3069\u2026","id":30118851488251904,"from_user_id":81492,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;"},{"from_user_id_str":"9518356","profile_image_url":"http://a2.twimg.com/profile_images/119165723/ocaml-icon_normal.png","created_at":"Wed, 26 Jan 2011 04:19:19 +0000","from_user":"planet_ocaml","id_str":"30117557788741632","metadata":{"result_type":"recent"},"to_user_id":null,"text":"I so miss #haskell type classes in #ocaml - i want to do something like refinement. Also why does ocaml not have... http://bit.ly/geYRwt","id":30117557788741632,"from_user_id":9518356,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"218059","profile_image_url":"http://a1.twimg.com/profile_images/1053837723/twitter-icon9_normal.jpg","created_at":"Wed, 26 Jan 2011 04:16:32 +0000","from_user":"aprikip","id_str":"30116854940835840","metadata":{"result_type":"recent"},"to_user_id":null,"text":"yatex-mode\u3084haskell-mode\u306e\u3053\u3068\u3067\u3059\u306d\u3001\u308f\u304b\u308a\u307e\u3059\u3002","id":30116854940835840,"from_user_id":218059,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://sites.google.com/site/yorufukurou/&quot; rel=&quot;nofollow&quot;&gt;YoruFukurou&lt;/a&gt;"},{"from_user_id_str":"216363","profile_image_url":"http://a1.twimg.com/profile_images/72454310/Tim-Avatar_normal.png","created_at":"Wed, 26 Jan 2011 04:15:30 +0000","from_user":"dysinger","id_str":"30116594684264448","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Haskell in Hawaii tonight for me... #fun","id":30116594684264448,"from_user_id":216363,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.nambu.com/&quot; rel=&quot;nofollow&quot;&gt;Nambu&lt;/a&gt;"},{"from_user_id_str":"1774820","profile_image_url":"http://a2.twimg.com/profile_images/61169291/dan_desert_thumb_normal.jpg","created_at":"Wed, 26 Jan 2011 04:13:36 +0000","from_user":"DanMil","id_str":"30116117682851840","metadata":{"result_type":"recent"},"to_user_id":1594784,"text":"@ojrac @chewedwire @tomheon Haskell isn't a language, it's a belief system.  A seductive one...","id":30116117682851840,"from_user_id":1774820,"to_user":"ojrac","geo":null,"iso_language_code":"en","to_user_id_str":"1594784","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"659256","profile_image_url":"http://a0.twimg.com/profile_images/746976711/angular-final_normal.jpg","created_at":"Wed, 26 Jan 2011 04:11:06 +0000","from_user":"djspiewak","id_str":"30115488931520512","metadata":{"result_type":"recent"},"to_user_id":null,"text":"One of the very nice things about Haskell as opposed to SML is the reduced proliferation of identifiers (e.g. andb, orb, etc). #typeclasses","id":30115488931520512,"from_user_id":659256,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://itunes.apple.com/us/app/twitter/id409789998?mt=12&quot; rel=&quot;nofollow&quot;&gt;Twitter for Mac&lt;/a&gt;"},{"from_user_id_str":"144546280","profile_image_url":"http://a1.twimg.com/a/1295051201/images/default_profile_1_normal.png","created_at":"Wed, 26 Jan 2011 04:06:12 +0000","from_user":"listwarenet","id_str":"30114255890026496","metadata":{"result_type":"recent"},"to_user_id":null,"text":"http://www.listware.net/201101/haskell-cafe/84752-re-haskell-cafe-gpl-license-of-h-matrix-and-prelude-numeric.html Re: Haskell-c","id":30114255890026496,"from_user_id":144546280,"geo":null,"iso_language_code":"no","to_user_id_str":null,"source":"&lt;a href=&quot;http://1e10.org/cloud/&quot; rel=&quot;nofollow&quot;&gt;1e10&lt;/a&gt;"},{"from_user_id_str":"1594784","profile_image_url":"http://a2.twimg.com/profile_images/378515773/square-profile_normal.jpg","created_at":"Wed, 26 Jan 2011 04:01:29 +0000","from_user":"ojrac","id_str":"30113067333324800","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @tomheon: @ojrac @chewedwire Don't worry, learning Haskell will not give you any clear idea what monad means.","id":30113067333324800,"from_user_id":1594784,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"207589736","profile_image_url":"http://a3.twimg.com/profile_images/1225527428/headshot_1_normal.jpg","created_at":"Wed, 26 Jan 2011 04:00:13 +0000","from_user":"ashleevelazq101","id_str":"30112747555397632","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Federal investigation finds safety violations at The Acadia Hospital: By Meg Haskell, BDN Staff The investigatio... http://bit.ly/dONnpn","id":30112747555397632,"from_user_id":207589736,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"17671137","profile_image_url":"http://a2.twimg.com/profile_images/290264834/Haskell-logo-outer-glow_normal.png","created_at":"Wed, 26 Jan 2011 03:58:00 +0000","from_user":"Hackage","id_str":"30112192346984448","metadata":{"result_type":"recent"},"to_user_id":null,"text":"streams 0.4, added by EdwardKmett: Various Haskell 2010 stream comonads http://bit.ly/idBkPe","id":30112192346984448,"from_user_id":17671137,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"17489366","profile_image_url":"http://a3.twimg.com/sticky/default_profile_images/default_profile_2_normal.png","created_at":"Wed, 26 Jan 2011 03:58:00 +0000","from_user":"aapnoot","id_str":"30112191881420800","metadata":{"result_type":"recent"},"to_user_id":null,"text":"streams 0.4, added by EdwardKmett: Various Haskell 2010 stream comonads http://bit.ly/idBkPe","id":30112191881420800,"from_user_id":17489366,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"8530482","profile_image_url":"http://a2.twimg.com/profile_images/137867266/n608671563_7396_normal.jpg","created_at":"Wed, 26 Jan 2011 03:50:12 +0000","from_user":"jeffmclamb","id_str":"30110229207187456","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Angel - daemon to run and monitor processes like daemontools or god, written in Haskell http://ff.im/-wNyLk","id":30110229207187456,"from_user_id":8530482,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://friendfeed.com&quot; rel=&quot;nofollow&quot;&gt;FriendFeed&lt;/a&gt;"},{"from_user_id_str":"177539201","profile_image_url":"http://a0.twimg.com/profile_images/1178368800/img_normal.jpeg","created_at":"Wed, 26 Jan 2011 03:46:01 +0000","from_user":"tomheon","id_str":"30109174645919744","metadata":{"result_type":"recent"},"to_user_id":1594784,"text":"@ojrac @chewedwire Don't worry, learning Haskell will not give you any clear idea what monad means.","id":30109174645919744,"from_user_id":177539201,"to_user":"ojrac","geo":null,"iso_language_code":"en","to_user_id_str":"1594784","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"1594784","profile_image_url":"http://a2.twimg.com/profile_images/378515773/square-profile_normal.jpg","created_at":"Wed, 26 Jan 2011 03:44:34 +0000","from_user":"ojrac","id_str":"30108808684503040","metadata":{"result_type":"recent"},"to_user_id":128028225,"text":"@chewedwire @tomheon Why are you making me curious about Haskell? I LIKE not knowing what monad means!!","id":30108808684503040,"from_user_id":1594784,"to_user":"chewedwire","geo":null,"iso_language_code":"en","to_user_id_str":"128028225","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"23750094","profile_image_url":"http://a0.twimg.com/profile_images/951373780/Moeinthecar_normal.jpg","created_at":"Wed, 26 Jan 2011 03:41:54 +0000","from_user":"shokalshab","id_str":"30108140443795456","metadata":{"result_type":"recent"},"to_user_id":null,"text":"I'm at Magnitude Cheer @ Gymnastics Olympica USA (7735 Haskell Ave., btw Saticoy &amp; Strathern, Van Nuys) http://4sq.com/gmXfaL","id":30108140443795456,"from_user_id":23750094,"geo":null,"iso_language_code":"en","place":{"id":"4e4a2a2f86cb2946","type_":"poi","full_name":"Gymnastics Olympica USA, Van Nuys"},"to_user_id_str":null,"source":"&lt;a href=&quot;http://foursquare.com&quot; rel=&quot;nofollow&quot;&gt;foursquare&lt;/a&gt;"},{"from_user_id_str":"8135112","profile_image_url":"http://a0.twimg.com/profile_images/1165240350/LIMITED_normal.jpg","created_at":"Wed, 26 Jan 2011 03:41:35 +0000","from_user":"Claricei","id_str":"30108059208515584","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @kristenmchugh22: @KeithOlbermann Ryan looks like Jughead w/o the hat. And sounds as mealy-mouthed as Eddie Haskell.","id":30108059208515584,"from_user_id":8135112,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"128028225","profile_image_url":"http://a1.twimg.com/profile_images/1224994593/Coding_Drunk_normal.jpg","created_at":"Wed, 26 Jan 2011 03:40:02 +0000","from_user":"chewedwire","id_str":"30107670367182848","metadata":{"result_type":"recent"},"to_user_id":177539201,"text":"@tomheon Cool, I'll take a look. I feel like I should mention this: http://bit.ly/hVstDM","id":30107670367182848,"from_user_id":128028225,"to_user":"tomheon","geo":null,"iso_language_code":"en","to_user_id_str":"177539201","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"8679778","profile_image_url":"http://a3.twimg.com/profile_images/1195318056/me_nyc_12_18_10_icon_normal.jpg","created_at":"Wed, 26 Jan 2011 03:38:42 +0000","from_user":"kristenmchugh22","id_str":"30107332381777920","metadata":{"result_type":"recent"},"to_user_id":756269,"text":"@KeithOlbermann Ryan looks like Jughead w/o the hat. And sounds as mealy-mouthed as Eddie Haskell.","id":30107332381777920,"from_user_id":8679778,"to_user":"KeithOlbermann","geo":null,"iso_language_code":"en","to_user_id_str":"756269","source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"103316559","profile_image_url":"http://a1.twimg.com/profile_images/1179458751/bc3beab4-d59d-4e78-b13b-50747986cfa2_normal.png","created_at":"Wed, 26 Jan 2011 03:36:15 +0000","from_user":"cityslikr","id_str":"30106719153557504","metadata":{"result_type":"recent"},"to_user_id":null,"text":"&quot;Social safety net into a hammock.&quot; So says Eddie Haskell with the GOP response. #SOTU","id":30106719153557504,"from_user_id":103316559,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://blackberry.com/twitter&quot; rel=&quot;nofollow&quot;&gt;Twitter for BlackBerry\u00ae&lt;/a&gt;"},{"from_user_id_str":"169063143","profile_image_url":"http://a0.twimg.com/profile_images/1160506212/9697_1_normal.gif","created_at":"Wed, 26 Jan 2011 03:31:52 +0000","from_user":"wrkforce_safety","id_str":"30105614919143424","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Federal investigation finds safety violations at The Acadia Hospital: By Meg Haskell, BDN Staff The investigatio... http://bit.ly/gA60C1","id":30105614919143424,"from_user_id":169063143,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"177539201","profile_image_url":"http://a0.twimg.com/profile_images/1178368800/img_normal.jpeg","created_at":"Wed, 26 Jan 2011 03:29:40 +0000","from_user":"tomheon","id_str":"30105060960632832","metadata":{"result_type":"recent"},"to_user_id":128028225,"text":"@chewedwire Great book on Haskell: http://oreilly.com/catalog/9780596514983","id":30105060960632832,"from_user_id":177539201,"to_user":"chewedwire","geo":null,"iso_language_code":"en","to_user_id_str":"128028225","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"782692","profile_image_url":"http://a0.twimg.com/profile_images/1031304589/Profile.2007.1_normal.jpg","created_at":"Wed, 26 Jan 2011 03:29:19 +0000","from_user":"turnageb","id_str":"30104974591533057","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @ovillalon: Paul Ryan solves the mystery of whatever happened to Eddie Haskell. #sotu","id":30104974591533057,"from_user_id":782692,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"128028225","profile_image_url":"http://a1.twimg.com/profile_images/1224994593/Coding_Drunk_normal.jpg","created_at":"Wed, 26 Jan 2011 03:28:04 +0000","from_user":"chewedwire","id_str":"30104657267265536","metadata":{"result_type":"recent"},"to_user_id":177539201,"text":"@tomheon I always loved the pattern matching in SML and it looks like Haskell is MUCH better at it. I'm messing around now at tryhaskell.org","id":30104657267265536,"from_user_id":128028225,"to_user":"tomheon","geo":null,"iso_language_code":"en","to_user_id_str":"177539201","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"12834082","profile_image_url":"http://a3.twimg.com/profile_images/1083036140/mugshot_normal.png","created_at":"Wed, 26 Jan 2011 03:28:01 +0000","from_user":"ovillalon","id_str":"30104647213518848","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Paul Ryan solves the mystery of whatever happened to Eddie Haskell. #sotu","id":30104647213518848,"from_user_id":12834082,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"13540930","profile_image_url":"http://a1.twimg.com/profile_images/1205312219/23467_350321383101_821143101_5114147_3010041_n_normal.jpg","created_at":"Wed, 26 Jan 2011 03:26:02 +0000","from_user":"goodfox","id_str":"30104146455560192","metadata":{"result_type":"recent"},"to_user_id":10226179,"text":"@billykeene22 Bordeaux is one of my heroes. I was so excited when he accepted the invitation to campus. He's been a great friend to Haskell.","id":30104146455560192,"from_user_id":13540930,"to_user":"billykeene22","geo":null,"iso_language_code":"en","to_user_id_str":"10226179","source":"&lt;a href=&quot;http://www.ubertwitter.com/bb/download.php&quot; rel=&quot;nofollow&quot;&gt;\u00dcberTwitter&lt;/a&gt;"},{"from_user_id_str":"18616016","profile_image_url":"http://a2.twimg.com/profile_images/1173522726/214397343_normal.jpg","created_at":"Wed, 26 Jan 2011 03:25:18 +0000","from_user":"josej30","id_str":"30103962313031681","metadata":{"result_type":"recent"},"to_user_id":14870909,"text":"@cris7ian Ahh bueno multiparadigma ya es respetable :) Empezar\u00e9 a explotar la parte funcional de los lenguajes ahora #Haskell","id":30103962313031681,"from_user_id":18616016,"to_user":"Cris7ian","geo":null,"iso_language_code":"es","to_user_id_str":"14870909","source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"14870909","profile_image_url":"http://a2.twimg.com/profile_images/1176930429/oso_yo_normal.png","created_at":"Wed, 26 Jan 2011 03:23:43 +0000","from_user":"Cris7ian","id_str":"30103562360983553","metadata":{"result_type":"recent"},"to_user_id":18616016,"text":"@josej30 hahaha no, es multiparadigma y es bastante lazy. Nothing like haskell, pero s\u00ed, el de Flash","id":30103562360983553,"from_user_id":14870909,"to_user":"josej30","geo":null,"iso_language_code":"es","to_user_id_str":"18616016","source":"&lt;a href=&quot;http://www.echofon.com/&quot; rel=&quot;nofollow&quot;&gt;Echofon&lt;/a&gt;"},{"from_user_id_str":"2421643","profile_image_url":"http://a0.twimg.com/profile_images/1190361665/ernestgrumbles-17_normal.jpg","created_at":"Wed, 26 Jan 2011 03:20:24 +0000","from_user":"ernestgrumbles","id_str":"30102730756333568","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Wow... WolframAlpha did not know who Eddie Haskell is.  Guess I'll never use that &quot;knowledge engine&quot; again.","id":30102730756333568,"from_user_id":2421643,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"128028225","profile_image_url":"http://a1.twimg.com/profile_images/1224994593/Coding_Drunk_normal.jpg","created_at":"Wed, 26 Jan 2011 03:14:21 +0000","from_user":"chewedwire","id_str":"30101204428132352","metadata":{"result_type":"recent"},"to_user_id":177539201,"text":"@tomheon How is Haskell better/different from CL or Scheme? I honestly don't know, although I'm becoming more curious.","id":30101204428132352,"from_user_id":128028225,"to_user":"tomheon","geo":null,"iso_language_code":"en","to_user_id_str":"177539201","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"13540930","profile_image_url":"http://a1.twimg.com/profile_images/1205312219/23467_350321383101_821143101_5114147_3010041_n_normal.jpg","created_at":"Wed, 26 Jan 2011 03:06:54 +0000","from_user":"goodfox","id_str":"30099329809129473","metadata":{"result_type":"recent"},"to_user_id":null,"text":"A day of vision &amp; speeches. #SOTU now. And a wonderful Haskell Convocation address earlier today by Sinte Gleske President Lionel Bordeaux.","id":30099329809129473,"from_user_id":13540930,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.ubertwitter.com/bb/download.php&quot; rel=&quot;nofollow&quot;&gt;\u00dcberTwitter&lt;/a&gt;"},{"from_user_id_str":"119185220","profile_image_url":"http://a0.twimg.com/profile_images/1089027228/dfg_normal.jpg","created_at":"Wed, 26 Jan 2011 03:03:38 +0000","from_user":"LaLiciouz_03","id_str":"30098510623805440","metadata":{"result_type":"recent"},"to_user_id":null,"text":"The Game with the girl room 330 Haskell follow us...","id":30098510623805440,"from_user_id":119185220,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"18616016","profile_image_url":"http://a2.twimg.com/profile_images/1173522726/214397343_normal.jpg","created_at":"Wed, 26 Jan 2011 02:57:25 +0000","from_user":"josej30","id_str":"30096946144215040","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Voy a extra\u00f1ar Haskell cuando regrese al mundo imperativo. Hay alg\u00fan lenguaje imperativo que tenga este poder funcional? #ci3661","id":30096946144215040,"from_user_id":18616016,"geo":null,"iso_language_code":"es","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"17671137","profile_image_url":"http://a2.twimg.com/profile_images/290264834/Haskell-logo-outer-glow_normal.png","created_at":"Wed, 26 Jan 2011 02:55:32 +0000","from_user":"Hackage","id_str":"30096471814574080","metadata":{"result_type":"recent"},"to_user_id":null,"text":"comonad-transformers 0.9.0, added by EdwardKmett: Haskell 98 comonad transformers http://bit.ly/h6xIsf","id":30096471814574080,"from_user_id":17671137,"geo":null,"iso_language_code":"no","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"177539201","profile_image_url":"http://a0.twimg.com/profile_images/1178368800/img_normal.jpeg","created_at":"Wed, 26 Jan 2011 02:48:12 +0000","from_user":"tomheon","id_str":"30094626920603649","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Every time I look at Haskell I love it more.","id":30094626920603649,"from_user_id":177539201,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"60792568","profile_image_url":"http://a0.twimg.com/profile_images/1203647517/glenda-flash_normal.jpg","created_at":"Wed, 26 Jan 2011 02:40:42 +0000","from_user":"r_takaishi","id_str":"30092735935422464","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Haskell\u3088\u308aD\u8a00\u8a9e\u304c\u4e0a\u3068\u306f\u601d\u308f\u306a\u304b\u3063\u305f\uff0e http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html","id":30092735935422464,"from_user_id":60792568,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twmode.sf.net/&quot; rel=&quot;nofollow&quot;&gt;twmode&lt;/a&gt;"},{"from_user_id_str":"160145510","profile_image_url":"http://a0.twimg.com/profile_images/1218108166/going_galt_normal.jpg","created_at":"Wed, 26 Jan 2011 02:39:31 +0000","from_user":"wtp1787","id_str":"30092439653974018","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @KLSouth: Eddie Haskell Goes to Washington...  &quot;You look really nice tonight, Ms Cleaver&quot;","id":30092439653974018,"from_user_id":160145510,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"96616016","profile_image_url":"http://a1.twimg.com/profile_images/1196978169/Picture0002_normal.jpg","created_at":"Wed, 26 Jan 2011 02:39:20 +0000","from_user":"MelissaRNMBA","id_str":"30092392203816960","metadata":{"result_type":"recent"},"to_user_id":14862975,"text":"@KLSouth At least Eddie Haskell was entertaining.","id":30092392203816960,"from_user_id":96616016,"to_user":"KLSouth","geo":null,"iso_language_code":"en","to_user_id_str":"14862975","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"14862975","profile_image_url":"http://a0.twimg.com/profile_images/421596393/kls_4_normal.JPG","created_at":"Wed, 26 Jan 2011 02:38:29 +0000","from_user":"KLSouth","id_str":"30092178327871489","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Eddie Haskell Goes to Washington...  &quot;You look really nice tonight, Ms Cleaver&quot;","id":30092178327871489,"from_user_id":14862975,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"144546280","profile_image_url":"http://a1.twimg.com/a/1295051201/images/default_profile_1_normal.png","created_at":"Wed, 26 Jan 2011 02:36:17 +0000","from_user":"listwarenet","id_str":"30091626869161984","metadata":{"result_type":"recent"},"to_user_id":null,"text":"http://www.listware.net/201101/haskell-beginners/84641-haskell-beginners-wildcards-in-expressions.html Haskell-beginners -  Wild","id":30091626869161984,"from_user_id":144546280,"geo":null,"iso_language_code":"no","to_user_id_str":null,"source":"&lt;a href=&quot;http://1e10.org/cloud/&quot; rel=&quot;nofollow&quot;&gt;1e10&lt;/a&gt;"},{"from_user_id_str":"24538048","profile_image_url":"http://a2.twimg.com/profile_images/1117267605/rope_normal.jpg","created_at":"Wed, 26 Jan 2011 02:23:14 +0000","from_user":"dbph","id_str":"30088341030440960","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @dnene: Skilled Calisthenics. Haskell code that outputs python which spits ruby which emits the haskell source. http://j.mp/YlQUL via @mfeathers","id":30088341030440960,"from_user_id":24538048,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"158951567","profile_image_url":"http://a3.twimg.com/profile_images/962721419/Logo_3_normal.jpg","created_at":"Wed, 26 Jan 2011 01:58:31 +0000","from_user":"YubaVetTech","id_str":"30082124207886336","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Dr. Haskell has just been awarded the prestigious Hayward Award for \u2018Excellence in Education\u2019. This award honors... http://fb.me/QgQCxd74","id":30082124207886336,"from_user_id":158951567,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.facebook.com/twitter&quot; rel=&quot;nofollow&quot;&gt;Facebook&lt;/a&gt;"},{"from_user_id_str":"158951567","profile_image_url":"http://a3.twimg.com/profile_images/962721419/Logo_3_normal.jpg","created_at":"Wed, 26 Jan 2011 01:56:04 +0000","from_user":"YubaVetTech","id_str":"30081505476743168","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Dr. Haskell has just been awarded the prestigous Haward Award for Excellence in Education. This award honors... http://fb.me/PC9mYmCR","id":30081505476743168,"from_user_id":158951567,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.facebook.com/twitter&quot; rel=&quot;nofollow&quot;&gt;Facebook&lt;/a&gt;"},{"from_user_id_str":"2331498","profile_image_url":"http://a3.twimg.com/profile_images/494632120/me_normal.jpg","created_at":"Wed, 26 Jan 2011 01:43:50 +0000","from_user":"Verus","id_str":"30078427155406848","metadata":{"result_type":"recent"},"to_user_id":79273052,"text":"@kami_joe \u3044\u3084\uff0c\u8ab2\u984c\u306f\u89e3\u6c7a\u5bfe\u8c61(\u30d1\u30ba\u30eb\u3068\u304b)\u3092\u4e0e\u3048\u3089\u308c\u3066\uff0c\u554f\u984c\u5b9a\u7fa9\u3068\u30d7\u30ed\u30b0\u30e9\u30df\u30f3\u30b0(\u6307\u5b9a\u8a00\u8a9e\u306fC++\u3082\u3057\u304f\u306fJava)\u3068\u3044\u3046\u3044\u308f\u3070\u666e\u901a\u306a\u8ab2\u984c\u3067\u306f\u3042\u308b\u3093\u3060\u3051\u3069\uff0e\u95a2\u6570\u578b\u8a00\u8a9e\u306fHaskell\u306e\u6388\u696d\u304c\u307e\u305f\u5225\u306b\u3042\u308b\u306e\uff0e","id":30078427155406848,"from_user_id":2331498,"to_user":"kami_joe","geo":null,"iso_language_code":"ja","to_user_id_str":"79273052","source":"&lt;a href=&quot;http://itunes.apple.com/us/app/twitter/id409789998?mt=12&quot; rel=&quot;nofollow&quot;&gt;Twitter for Mac&lt;/a&gt;"},{"from_user_id_str":"79151233","profile_image_url":"http://a2.twimg.com/a/1295051201/images/default_profile_2_normal.png","created_at":"Wed, 26 Jan 2011 01:40:37 +0000","from_user":"cz_newdrafts","id_str":"30077617361125376","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Haskell programming language http://bit.ly/gpPAwB","id":30077617361125376,"from_user_id":79151233,"geo":null,"iso_language_code":"no","to_user_id_str":null,"source":"&lt;a href=&quot;http://tommorris.org/&quot; rel=&quot;nofollow&quot;&gt;tommorris' hacksample&lt;/a&gt;"},{"from_user_id_str":"2331498","profile_image_url":"http://a3.twimg.com/profile_images/494632120/me_normal.jpg","created_at":"Wed, 26 Jan 2011 01:02:57 +0000","from_user":"Verus","id_str":"30068139416879104","metadata":{"result_type":"recent"},"to_user_id":9252720,"text":"@shukukei Java\u3068C\u306f\u3042\u308b\u7a0b\u5ea6\u66f8\u3051\u3066\u3042\u305f\u308a\u307e\u3048\u306a\u3068\u3053\u308d\u304c\u3042\u308b\u304b\u3089\u306a\u30fc\uff0ePython\u306f\u500b\u4eba\u7684\u306b\u611f\u899a\u304c\u5408\u308f\u306a\u3044\uff0e\u611f\u899a\u306a\u306e\u3067\uff0c\u3082\u3046\u3069\u3046\u3057\u3088\u3046\u3082\u306a\u3044\uff57 \u3044\u307e\u306fScala\u3068Haskell\u3092\u3082\u3063\u3068\u6975\u3081\u305f\u3044\u3068\u3053\u308d\uff0e","id":30068139416879104,"from_user_id":2331498,"to_user":"shukukei","geo":null,"iso_language_code":"ja","to_user_id_str":"9252720","source":"&lt;a href=&quot;http://itunes.apple.com/us/app/twitter/id409789998?mt=12&quot; rel=&quot;nofollow&quot;&gt;Twitter for Mac&lt;/a&gt;"},{"from_user_id_str":"2781460","profile_image_url":"http://a0.twimg.com/profile_images/82526625/.joeyicon_normal.jpg","created_at":"Wed, 26 Jan 2011 01:02:35 +0000","from_user":"joeyhess","id_str":"30068046538219520","metadata":{"result_type":"recent"},"to_user_id":null,"text":"just figured out that I can use parameterized types to remove a dependency loop in git-annex's type definitions. whee #haskell","id":30068046538219520,"from_user_id":2781460,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://identi.ca&quot; rel=&quot;nofollow&quot;&gt;identica&lt;/a&gt;"},{"from_user_id_str":"144546280","profile_image_url":"http://a1.twimg.com/a/1295051201/images/default_profile_1_normal.png","created_at":"Wed, 26 Jan 2011 00:54:22 +0000","from_user":"listwarenet","id_str":"30065977920061440","metadata":{"result_type":"recent"},"to_user_id":null,"text":"http://www.listware.net/201101/haskell-beginners/84466-haskell-beginners-bytestring-question.html Haskell-beginners -  Bytestrin","id":30065977920061440,"from_user_id":144546280,"geo":null,"iso_language_code":"no","to_user_id_str":null,"source":"&lt;a href=&quot;http://1e10.org/cloud/&quot; rel=&quot;nofollow&quot;&gt;1e10&lt;/a&gt;"},{"from_user_id_str":"1291845","profile_image_url":"http://a0.twimg.com/profile_images/1225743404/ThinOxygen-small-opaque-solidarity_normal.png","created_at":"Wed, 26 Jan 2011 00:52:07 +0000","from_user":"_aaron_","id_str":"30065412242669568","metadata":{"result_type":"recent"},"to_user_id":null,"text":"wanted: librly licensed high level native compiled lang with min runtime (otherwise cobra/mono would be perfect) for win. lua? haskell? ooc?","id":30065412242669568,"from_user_id":1291845,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"5912444","profile_image_url":"http://a2.twimg.com/profile_images/546261026/ssf0xg11_normal.jpg","created_at":"Wed, 26 Jan 2011 00:45:45 +0000","from_user":"shelarcy","id_str":"30063810773516288","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @Hackage: download 0.3.1.1, added by MagnusTherning: High-level file download based on URLs http://bit.ly/eHfiJB","id":30063810773516288,"from_user_id":5912444,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"135993","profile_image_url":"http://a2.twimg.com/profile_images/1190667086/myface2010small_normal.jpg","created_at":"Wed, 26 Jan 2011 00:41:00 +0000","from_user":"kudzu_naoki","id_str":"30062613287141376","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @tanakh: \u9b54\u6cd5Haskell\u5c11\u5973\u5019\u88dc\u3092\u63a2\u3059\u306e\u3082\u5927\u5909\u306a\u3093\u3060","id":30062613287141376,"from_user_id":135993,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;"},{"from_user_id_str":"52620546","profile_image_url":"http://a1.twimg.com/profile_images/434693356/twitter-icon_normal.png","created_at":"Wed, 26 Jan 2011 00:40:25 +0000","from_user":"omasanori","id_str":"30062465656033280","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u9b54\u6cd5Haskell\u5c11\u5973\u306e\u4e00\u65e5\u306fGHC HEAD\u306e\u30d3\u30eb\u30c9\u304b\u3089\u59cb\u307e\u308b","id":30062465656033280,"from_user_id":52620546,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.movatwi.jp&quot; rel=&quot;nofollow&quot;&gt;www.movatwi.jp&lt;/a&gt;"},{"from_user_id_str":"52620546","profile_image_url":"http://a1.twimg.com/profile_images/434693356/twitter-icon_normal.png","created_at":"Wed, 26 Jan 2011 00:37:12 +0000","from_user":"omasanori","id_str":"30061656331526144","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @tanakh: \u50d5\u3068\u5951\u7d04\u3057\u3066\u9b54\u6cd5Haskell\u5c11\u5973\u306b\u306a\u308d\u3046\u3088\uff01","id":30061656331526144,"from_user_id":52620546,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;"},{"from_user_id_str":"135993","profile_image_url":"http://a2.twimg.com/profile_images/1190667086/myface2010small_normal.jpg","created_at":"Wed, 26 Jan 2011 00:35:42 +0000","from_user":"kudzu_naoki","id_str":"30061282665168896","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @tanakh: \u50d5\u3068\u5951\u7d04\u3057\u3066\u9b54\u6cd5Haskell\u5c11\u5973\u306b\u306a\u308d\u3046\u3088\uff01","id":30061282665168896,"from_user_id":135993,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;"},{"from_user_id_str":"1631333","profile_image_url":"http://a1.twimg.com/profile_images/81268862/profile300_normal.jpg","created_at":"Wed, 26 Jan 2011 00:30:02 +0000","from_user":"qnighy","id_str":"30059855884582913","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @tanakh: \u50d5\u3068\u5951\u7d04\u3057\u3066\u9b54\u6cd5Haskell\u5c11\u5973\u306b\u306a\u308d\u3046\u3088\uff01","id":30059855884582913,"from_user_id":1631333,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;"},{"from_user_id_str":"9093754","profile_image_url":"http://a1.twimg.com/profile_images/99435906/PHD_3d_col_blk_normal.jpg","created_at":"Wed, 26 Jan 2011 00:26:20 +0000","from_user":"phdwine","id_str":"30058925516656640","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Last weekend of Tri Nations Tasting with PHD wines at Haskell Vineyards. Hope you didn't miss the Pinot Noir tasting last week !","id":30058925516656640,"from_user_id":9093754,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"2119923","profile_image_url":"http://a2.twimg.com/profile_images/1096701078/djayprofile_normal.jpg","created_at":"Wed, 26 Jan 2011 00:21:14 +0000","from_user":"djay75","id_str":"30057639543050240","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @dnene: Skilled Calisthenics. Haskell code that outputs python which spits ruby which emits the haskell source. http://j.mp/YlQUL via @mfeathers","id":30057639543050240,"from_user_id":2119923,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"134323731","profile_image_url":"http://a2.twimg.com/profile_images/1225818824/IMG00518-20110125-1547_normal.jpg","created_at":"Wed, 26 Jan 2011 00:19:30 +0000","from_user":"PrinceOfBrkeley","id_str":"30057205554216960","metadata":{"result_type":"recent"},"to_user_id":167093027,"text":"@PayThaPrince go to haskell.","id":30057205554216960,"from_user_id":134323731,"to_user":"PayThaPrince","geo":null,"iso_language_code":"en","to_user_id_str":"167093027","source":"&lt;a href=&quot;http://blackberry.com/twitter&quot; rel=&quot;nofollow&quot;&gt;Twitter for BlackBerry\u00ae&lt;/a&gt;"},{"from_user_id_str":"705857","profile_image_url":"http://a1.twimg.com/profile_images/429483912/ben_twitter_normal.jpg","created_at":"Wed, 26 Jan 2011 00:03:28 +0000","from_user":"bennadel","id_str":"30053166972141569","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Using #Homebrew to install Haskell - the last of the Seven Languages (in Seven Weeks) http://bit.ly/eA9Cv1 This has been some journey.","id":30053166972141569,"from_user_id":705857,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"251466","profile_image_url":"http://a2.twimg.com/profile_images/1194100934/pass_normal.jpg","created_at":"Wed, 26 Jan 2011 00:02:54 +0000","from_user":"simonszu","id_str":"30053025502466048","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Ouh yeah! Ich werde doch produktiv sein, diese Semesterferien. Ich werde funktional Programmieren lernen. #haskell, Baby.","id":30053025502466048,"from_user_id":251466,"geo":null,"iso_language_code":"de","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.echofon.com/&quot; rel=&quot;nofollow&quot;&gt;Echofon&lt;/a&gt;"},{"from_user_id_str":"357786","profile_image_url":"http://a0.twimg.com/profile_images/612044841/FM_2010_normal.jpg","created_at":"Tue, 25 Jan 2011 23:59:13 +0000","from_user":"diligiant","id_str":"30052100973006848","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @paulrbrown: &quot;...we only discovered a single bug after compilation.&quot; (http://t.co/K0wlEYz) #haskell","id":30052100973006848,"from_user_id":357786,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://itunes.apple.com/us/app/twitter/id409789998?mt=12&quot; rel=&quot;nofollow&quot;&gt;Twitter for Mac&lt;/a&gt;"},{"from_user_id_str":"101597523","profile_image_url":"http://a1.twimg.com/profile_images/1206183256/2010-12-12-204902_normal.jpg","created_at":"Tue, 25 Jan 2011 23:54:46 +0000","from_user":"wlad_kent","id_str":"30050977964892160","metadata":{"result_type":"recent"},"to_user_id":86297184,"text":"@Nilson_Neto Isso eh bem basico. quando vc estiver estudando recurs\u00e3o em Haskell, ai vc vai ver oq eh papo de nerd - kkk - 1\u00ba periodo isso.","id":30050977964892160,"from_user_id":101597523,"to_user":"Nilson_Neto","geo":null,"iso_language_code":"pt","to_user_id_str":"86297184","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"144546280","profile_image_url":"http://a1.twimg.com/a/1295051201/images/default_profile_1_normal.png","created_at":"Tue, 25 Jan 2011 23:51:22 +0000","from_user":"listwarenet","id_str":"30050123383832577","metadata":{"result_type":"recent"},"to_user_id":null,"text":"http://www.listware.net/201101/haskell-cafe/84336-haskell-cafe-monomorphic-let-bindings-and-darcs.html Haskell-cafe -  Monomorph","id":30050123383832577,"from_user_id":144546280,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://1e10.org/cloud/&quot; rel=&quot;nofollow&quot;&gt;1e10&lt;/a&gt;"},{"from_user_id_str":"5776901","profile_image_url":"http://a0.twimg.com/profile_images/472682157/lucabw_normal.JPG","created_at":"Tue, 25 Jan 2011 23:48:40 +0000","from_user":"lsbardel","id_str":"30049446469312512","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Very interesting technology stack these guys use http://bit.ly/ghAjS7 #python #redis #haskell","id":30049446469312512,"from_user_id":5776901,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"3005901","profile_image_url":"http://a0.twimg.com/profile_images/278943006/Lone_Pine_Peak_-_Peter_200x200_normal.jpg","created_at":"Tue, 25 Jan 2011 23:45:48 +0000","from_user":"pmonks","id_str":"30048721110573056","metadata":{"result_type":"recent"},"to_user_id":203834278,"text":"@techielicous Haskell: http://bit.ly/gy3oFi  ;-)","id":30048721110573056,"from_user_id":3005901,"to_user":"techielicous","geo":null,"iso_language_code":"en","to_user_id_str":"203834278","source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"162697074","profile_image_url":"http://a2.twimg.com/profile_images/826205838/ryan3_normal.jpg","created_at":"Tue, 25 Jan 2011 23:44:47 +0000","from_user":"TheRonaldMCD","id_str":"30048468781244416","metadata":{"result_type":"recent"},"to_user_id":null,"text":"I'm at Aldi Food Market (4122 Gaston Ave, Haskell, Dallas) http://4sq.com/ekqRY7","id":30048468781244416,"from_user_id":162697074,"geo":null,"iso_language_code":"no","to_user_id_str":null,"source":"&lt;a href=&quot;http://foursquare.com&quot; rel=&quot;nofollow&quot;&gt;foursquare&lt;/a&gt;"},{"from_user_id_str":"203834278","profile_image_url":"http://a0.twimg.com/profile_images/1223375080/me_normal.jpg","created_at":"Tue, 25 Jan 2011 23:44:04 +0000","from_user":"techielicous","id_str":"30048286589067264","metadata":{"result_type":"recent"},"to_user_id":3005901,"text":"@pmonks I was thinking Haskell or Scheme","id":30048286589067264,"from_user_id":203834278,"to_user":"pmonks","geo":null,"iso_language_code":"en","to_user_id_str":"3005901","source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"537690","profile_image_url":"http://a3.twimg.com/profile_images/45665122/nushiostamp_normal.jpg","created_at":"Tue, 25 Jan 2011 23:38:14 +0000","from_user":"nushio","id_str":"30046818658164737","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u305d\u3046\u3044\u3084\u5951\u7d04\u306b\u3088\u308b\u30d7\u30ed\u30b0\u30e9\u30df\u30f3\u30b0\u3068\u304b\u3042\u3063\u305f\u306a\u3002Haskell\u306a\u3093\u304b\u3088\u308a\u3042\u3063\u3061\u306e\u65b9\u304c\u9b54\u6cd5\u5c11\u5973\u547c\u3070\u308f\u308a\u306b\u3075\u3055\u308f\u3057\u3044\u3002","id":30046818658164737,"from_user_id":537690,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"181192","profile_image_url":"http://a2.twimg.com/profile_images/1089890701/q530888600_9828_normal.jpg","created_at":"Tue, 25 Jan 2011 23:38:03 +0000","from_user":"aodag","id_str":"30046772223016960","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @omasanori: \u300c\u3082\u3046\u4f55\u5e74\u3082Haskell\u306e\u3053\u3068\u3057\u304b\u8003\u3048\u3066\u306a\u304b\u3063\u305f\u304b\u3089\u306a\u3001\u30eb\u30fc\u30d7\u306e\u4f7f\u3044\u65b9\u5fd8\u308c\u3066\u305f\u300d","id":30046772223016960,"from_user_id":181192,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.movatwi.jp&quot; rel=&quot;nofollow&quot;&gt;www.movatwi.jp&lt;/a&gt;"},{"from_user_id_str":"5886055","profile_image_url":"http://a3.twimg.com/profile_images/85531669/ichi_normal.jpg","created_at":"Tue, 25 Jan 2011 23:37:53 +0000","from_user":"kanaya","id_str":"30046729243983873","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u9e97\u3057\u3044\u3002\u201c@omasanori: \u300c\u3082\u3046\u4f55\u5e74\u3082Haskell\u306e\u3053\u3068\u3057\u304b\u8003\u3048\u3066\u306a\u304b\u3063\u305f\u304b\u3089\u306a\u3001\u30eb\u30fc\u30d7\u306e\u4f7f\u3044\u65b9\u5fd8\u308c\u3066\u305f\u300d\u201d","id":30046729243983873,"from_user_id":5886055,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://itunes.apple.com/app/twitter/id333903271?mt=8&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPad&lt;/a&gt;"},{"from_user_id_str":"52620546","profile_image_url":"http://a1.twimg.com/profile_images/434693356/twitter-icon_normal.png","created_at":"Tue, 25 Jan 2011 23:36:26 +0000","from_user":"omasanori","id_str":"30046367187476481","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u300c\u3082\u3046\u4f55\u5e74\u3082Haskell\u306e\u3053\u3068\u3057\u304b\u8003\u3048\u3066\u306a\u304b\u3063\u305f\u304b\u3089\u306a\u3001\u30eb\u30fc\u30d7\u306e\u4f7f\u3044\u65b9\u5fd8\u308c\u3066\u305f\u300d","id":30046367187476481,"from_user_id":52620546,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.movatwi.jp&quot; rel=&quot;nofollow&quot;&gt;www.movatwi.jp&lt;/a&gt;"},{"from_user_id_str":"1659992","profile_image_url":"http://a1.twimg.com/profile_images/504488750/1_normal.jpg","created_at":"Tue, 25 Jan 2011 23:16:13 +0000","from_user":"finalfusion","id_str":"30041278544609280","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @necocen: Haskell\u306e\u30df\u30b5\u30ef\u3001\u3068\u3044\u3046\u3082\u306e\u3092\u8003\u3048\u3066\u307f\u3066\u3071\u3063\u3068\u3057\u306a\u3044","id":30041278544609280,"from_user_id":1659992,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"272513","profile_image_url":"http://a2.twimg.com/profile_images/320244078/Mike_Painting2_normal.png","created_at":"Tue, 25 Jan 2011 23:05:09 +0000","from_user":"mikehadlow","id_str":"30038492373319680","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Just written the same simple program in C#, F# and Haskell: 18, 12 and 9 LoC respectively. And I'm much better at C# than F# or Haskell.","id":30038492373319680,"from_user_id":272513,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"89393264","profile_image_url":"http://a0.twimg.com/profile_images/1169486472/020_portland_me_normal.jpg","created_at":"Tue, 25 Jan 2011 23:01:28 +0000","from_user":"newsportlandme","id_str":"30037564589084673","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Governor's Choice to Run Maine DOC Under Scrutiny - MPBN: State Rep. Anne Haskell, a Portland Democrat, says Mai... http://bit.ly/fO30gC","id":30037564589084673,"from_user_id":89393264,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"38138","profile_image_url":"http://a3.twimg.com/profile_images/1212565885/Screen_shot_2011-01-11_at_5.28.47_PM_normal.png","created_at":"Tue, 25 Jan 2011 22:59:55 +0000","from_user":"michaelneale","id_str":"30037174615281664","metadata":{"result_type":"recent"},"to_user_id":null,"text":"The haskell EvilMangler http://t.co/P2Tls7J","id":30037174615281664,"from_user_id":38138,"geo":null,"iso_language_code":"no","to_user_id_str":null,"source":"&lt;a href=&quot;http://itunes.apple.com/us/app/twitter/id409789998?mt=12&quot; rel=&quot;nofollow&quot;&gt;Twitter for Mac&lt;/a&gt;"},{"from_user_id_str":"45692","profile_image_url":"http://a3.twimg.com/profile_images/1177741507/dogkarno_r_normal.jpg","created_at":"Tue, 25 Jan 2011 22:45:06 +0000","from_user":"karno","id_str":"30033448286552064","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Haskell\u3063\u3066\u95a2\u6570\u306b\u30ab\u30ec\u30fc\u7c89\u3076\u3061\u8fbc\u3080\u3068\u304b\u306a\u3093\u3068\u304b","id":30033448286552064,"from_user_id":45692,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://yubitter.com/&quot; rel=&quot;nofollow&quot;&gt;yubitter&lt;/a&gt;"},{"from_user_id_str":"27605","profile_image_url":"http://a2.twimg.com/profile_images/15826632/meron_normal.jpg","created_at":"Tue, 25 Jan 2011 22:44:23 +0000","from_user":"celeron1ghz","id_str":"30033268761956352","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @necocen: Haskell\u306e\u30df\u30b5\u30ef\u3001\u3068\u3044\u3046\u3082\u306e\u3092\u8003\u3048\u3066\u307f\u3066\u3071\u3063\u3068\u3057\u306a\u3044","id":30033268761956352,"from_user_id":27605,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"12901","profile_image_url":"http://a1.twimg.com/profile_images/56091349/haruhi_nagato-Y_normal.jpg","created_at":"Tue, 25 Jan 2011 22:43:30 +0000","from_user":"necocen","id_str":"30033044035346432","metadata":{"result_type":"recent"},"to_user_id":null,"text":"\u307e\u3042Haskell\u77e5\u3089\u3093\u3057\u306d","id":30033044035346432,"from_user_id":12901,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"12901","profile_image_url":"http://a1.twimg.com/profile_images/56091349/haruhi_nagato-Y_normal.jpg","created_at":"Tue, 25 Jan 2011 22:42:54 +0000","from_user":"necocen","id_str":"30032894856531968","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Haskell\u306e\u30df\u30b5\u30ef\u3001\u3068\u3044\u3046\u3082\u306e\u3092\u8003\u3048\u3066\u307f\u3066\u3071\u3063\u3068\u3057\u306a\u3044","id":30032894856531968,"from_user_id":12901,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"10821270","profile_image_url":"http://a0.twimg.com/profile_images/1195474259/Salto_Marlon_normal.jpg","created_at":"Tue, 25 Jan 2011 22:35:09 +0000","from_user":"jjedMoriAnktah","id_str":"30030942106025984","metadata":{"result_type":"recent"},"to_user_id":null,"text":"&quot;A Haskell program that outputs a Python program that outputs a Ruby program that outputs the original Haskell program&quot; http://is.gd/E6Julu","id":30030942106025984,"from_user_id":10821270,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"7554762","profile_image_url":"http://a0.twimg.com/profile_images/100515212/ajay-photo_normal.jpg","created_at":"Tue, 25 Jan 2011 22:34:30 +0000","from_user":"comatose_kid","id_str":"30030780205899776","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @bumptech: Bump Dev Blog - Why we use Haskell at Bump http://devblog.bu.mp/haskell-at-bump","id":30030780205899776,"from_user_id":7554762,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"148474705","profile_image_url":"http://a0.twimg.com/profile_images/1119234716/fur_hat_-_gilbeys_vodka_-_life_-_11-30-1962_normal.JPG","created_at":"Tue, 25 Jan 2011 22:29:19 +0000","from_user":"landmvintage","id_str":"30029476003840000","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @faerymoongodess: Magnificent Miriam Haskell Baroque Pearl Necklace by @MercyMadge http://etsy.me/hqRl51","id":30029476003840000,"from_user_id":148474705,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"37715508","profile_image_url":"http://a1.twimg.com/profile_images/1207891411/ballerina_normal.jpg","created_at":"Tue, 25 Jan 2011 22:24:52 +0000","from_user":"faerymoongodess","id_str":"30028356326002688","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Magnificent Miriam Haskell Baroque Pearl Necklace by @MercyMadge http://etsy.me/hqRl51","id":30028356326002688,"from_user_id":37715508,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"17671137","profile_image_url":"http://a2.twimg.com/profile_images/290264834/Haskell-logo-outer-glow_normal.png","created_at":"Tue, 25 Jan 2011 22:17:16 +0000","from_user":"Hackage","id_str":"30026442456694784","metadata":{"result_type":"recent"},"to_user_id":null,"text":"base16-bytestring 0.1.0.0, added by BryanOSullivan: Fast base16 (hex) encoding and deconding for ByteStrings http://bit.ly/eu75TN","id":30026442456694784,"from_user_id":17671137,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"199750997","profile_image_url":"http://a3.twimg.com/sticky/default_profile_images/default_profile_2_normal.png","created_at":"Tue, 25 Jan 2011 22:12:07 +0000","from_user":"benreads","id_str":"30025146991382528","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Writing Systems Software in a Functional Language -- brief, but fun. I want to see how well they can make Systems Haskell perform at scale.","id":30025146991382528,"from_user_id":199750997,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"35359797","profile_image_url":"http://a1.twimg.com/profile_images/1173453785/48893_521140819_1896062_q_normal.jpg","created_at":"Tue, 25 Jan 2011 22:09:45 +0000","from_user":"jcawthorne1","id_str":"30024549265309696","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Just saw Jeff Haskell give the middle finger!! Lol awesome","id":30024549265309696,"from_user_id":35359797,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"13540930","profile_image_url":"http://a1.twimg.com/profile_images/1205312219/23467_350321383101_821143101_5114147_3010041_n_normal.jpg","created_at":"Tue, 25 Jan 2011 22:06:44 +0000","from_user":"goodfox","id_str":"30023790381498369","metadata":{"result_type":"recent"},"to_user_id":null,"text":"At the Haskell Convocation. (@ Haskell Indian Nations U Auditorium) http://4sq.com/eKKXyn","id":30023790381498369,"from_user_id":13540930,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://foursquare.com&quot; rel=&quot;nofollow&quot;&gt;foursquare&lt;/a&gt;"},{"from_user_id_str":"14674418","profile_image_url":"http://a2.twimg.com/profile_images/238230125/IMG_1030-1_normal.jpg","created_at":"Tue, 25 Jan 2011 22:02:31 +0000","from_user":"sanityinc","id_str":"30022731919523840","metadata":{"result_type":"recent"},"to_user_id":371289,"text":"@xshay Haskell's great, but Clojure's similarly lazy in all the ways that matter, and is more practical for day-to-day use.","id":30022731919523840,"from_user_id":14674418,"to_user":"xshay","geo":null,"iso_language_code":"en","to_user_id_str":"371289","source":"&lt;a href=&quot;http://www.echofon.com/&quot; rel=&quot;nofollow&quot;&gt;Echofon&lt;/a&gt;"},{"from_user_id_str":"371289","profile_image_url":"http://a2.twimg.com/profile_images/1113482439/me-brisbane_normal.jpg","created_at":"Tue, 25 Jan 2011 21:58:29 +0000","from_user":"xshay","id_str":"30021714444292096","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Just made a lazy sequence of prime numbers in haskell. I'm smitten.","id":30021714444292096,"from_user_id":371289,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://itunes.apple.com/us/app/twitter/id409789998?mt=12&quot; rel=&quot;nofollow&quot;&gt;Twitter for Mac&lt;/a&gt;"},{"from_user_id_str":"199453873","profile_image_url":"http://a3.twimg.com/a/1294785484/images/default_profile_3_normal.png","created_at":"Tue, 25 Jan 2011 21:48:40 +0000","from_user":"stackfeed","id_str":"30019243982454784","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Practical Scala reference manual, for searching things like method names: Hello, \n\nInspired by\nHaskell API Searc... http://bit.ly/g4zWZQ","id":30019243982454784,"from_user_id":199453873,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"17489366","profile_image_url":"http://a3.twimg.com/sticky/default_profile_images/default_profile_2_normal.png","created_at":"Tue, 25 Jan 2011 21:45:32 +0000","from_user":"aapnoot","id_str":"30018455189065728","metadata":{"result_type":"recent"},"to_user_id":null,"text":"download 0.3.1, added by MagnusTherning: High-level file download based on URLs http://bit.ly/i8mYvi","id":30018455189065728,"from_user_id":17489366,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"17489366","profile_image_url":"http://a3.twimg.com/sticky/default_profile_images/default_profile_2_normal.png","created_at":"Tue, 25 Jan 2011 21:45:31 +0000","from_user":"aapnoot","id_str":"30018452601180160","metadata":{"result_type":"recent"},"to_user_id":null,"text":"download 0.3.1.1, added by MagnusTherning: High-level file download based on URLs http://bit.ly/eHfiJB","id":30018452601180160,"from_user_id":17489366,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"17671137","profile_image_url":"http://a2.twimg.com/profile_images/290264834/Haskell-logo-outer-glow_normal.png","created_at":"Tue, 25 Jan 2011 21:45:31 +0000","from_user":"Hackage","id_str":"30018451867176960","metadata":{"result_type":"recent"},"to_user_id":null,"text":"download 0.3.1, added by MagnusTherning: High-level file download based on URLs http://bit.ly/i8mYvi","id":30018451867176960,"from_user_id":17671137,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"138502705","profile_image_url":"http://a1.twimg.com/profile_images/1083845614/yclogo_normal.gif","created_at":"Tue, 25 Jan 2011 21:45:04 +0000","from_user":"newsyc100","id_str":"30018340839755777","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Haskell improves log processing 4x over Python http://devblog.bu.mp/haskell-at-bump (http://bit.ly/gQxxR8)","id":30018340839755777,"from_user_id":138502705,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://news.ycombinator.com&quot; rel=&quot;nofollow&quot;&gt;newsyc&lt;/a&gt;"},{"from_user_id_str":"1578246","profile_image_url":"http://a0.twimg.com/profile_images/59312315/avatar_simpson_small_normal.jpg","created_at":"Tue, 25 Jan 2011 21:43:32 +0000","from_user":"magthe","id_str":"30017953801969665","metadata":{"result_type":"recent"},"to_user_id":null,"text":"New version of download uploaded to #hackage #haskell","id":30017953801969665,"from_user_id":1578246,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://api.supertweet.net&quot; rel=&quot;nofollow&quot;&gt;MyAuth API Proxy&lt;/a&gt;"},{"from_user_id_str":"135838970","profile_image_url":"http://a2.twimg.com/profile_images/1079929891/logo_normal.png","created_at":"Tue, 25 Jan 2011 21:37:51 +0000","from_user":"reward999","id_str":"30016525180084224","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Found Great Dane (Main &amp; Haskell- Dallas): We found a great dane. 214-712-0000 http://bit.ly/fQ8iBw","id":30016525180084224,"from_user_id":135838970,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"137719265","profile_image_url":"http://a2.twimg.com/profile_images/1092224079/8__4__normal.jpg","created_at":"Tue, 25 Jan 2011 21:34:22 +0000","from_user":"WeiMatas","id_str":"30015646020411392","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Fourth day Rez party Eddie Haskell County in second life \u2013 which took place in cabaret Tadd","id":30015646020411392,"from_user_id":137719265,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://dlvr.it&quot; rel=&quot;nofollow&quot;&gt;dlvr.it&lt;/a&gt;"},{"from_user_id_str":"144546280","profile_image_url":"http://a1.twimg.com/a/1295051201/images/default_profile_1_normal.png","created_at":"Tue, 25 Jan 2011 21:33:21 +0000","from_user":"listwarenet","id_str":"30015392571195392","metadata":{"result_type":"recent"},"to_user_id":null,"text":"http://www.listware.net/201101/haskell-cafe/83889-haskell-cafe-gpl-license-of-h-matrix-and-prelude-numeric.html Haskell-cafe -","id":30015392571195392,"from_user_id":144546280,"geo":null,"iso_language_code":"no","to_user_id_str":null,"source":"&lt;a href=&quot;http://1e10.org/cloud/&quot; rel=&quot;nofollow&quot;&gt;1e10&lt;/a&gt;"}],"max_id":30121530767708160,"since_id":0,"refresh_url":"?since_id=30121530767708160&q=haskell","next_page":"?page=2&max_id=30121530767708160&rpp=100&q=haskell","results_per_page":100,"page":1,"completed_in":1.195569,"since_id_str":"0","max_id_str":"30121530767708160","query":"haskell"}
+ test/json-data/twitter20.json view
@@ -0,0 +1,1 @@+{"results":[{"from_user_id_str":"166199691","profile_image_url":"http://a2.twimg.com/profile_images/1252958188/38_normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:26 +0000","from_user":"_classicc","id_str":"41191052790603776","metadata":{"result_type":"recent"},"to_user_id":null,"text":"my twitter is actin slow today.","id":41191052790603776,"from_user_id":166199691,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"138835410","profile_image_url":"http://a1.twimg.com/profile_images/1250985094/243321801_normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:26 +0000","from_user":"amereronday","id_str":"41191050307567616","metadata":{"result_type":"recent"},"to_user_id":null,"text":"twitter jail, here i come.","id":41191050307567616,"from_user_id":138835410,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"359548","profile_image_url":"http://a2.twimg.com/profile_images/53612334/don_otvos_normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:26 +0000","from_user":"donnyo","id_str":"41191050110451712","metadata":{"result_type":"recent"},"to_user_id":7074534,"text":"@stlsmallbiz I see there is currently a Twitter promo too....tempting....","id":41191050110451712,"from_user_id":359548,"to_user":"stlsmallbiz","geo":null,"iso_language_code":"en","place":{"id":"82b7b2f97b12261d","type_":"poi","full_name":"Yammer Inc, San Francisco"},"to_user_id_str":"7074534","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"122523770","profile_image_url":"http://a0.twimg.com/profile_images/1073533262/11646_1076222405045_1810798478_156114_3663737_n_normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:26 +0000","from_user":"msBreChan","id_str":"41191049720238080","metadata":{"result_type":"recent"},"to_user_id":null,"text":"so twitter gt spam now -_-","id":41191049720238080,"from_user_id":122523770,"geo":{"type_":"Point","coordinates":[35.2213,-80.8276]},"iso_language_code":"en","place":{"id":"4d5ed95f830e9b41","type_":"neighborhood","full_name":"Elizabeth, Charlotte"},"to_user_id_str":null,"source":"&lt;a href=&quot;http://mobile.twitter.com&quot; rel=&quot;nofollow&quot;&gt;Twitter for Android&lt;/a&gt;"},{"from_user_id_str":"229024950","profile_image_url":"http://a3.twimg.com/sticky/default_profile_images/default_profile_6_normal.png","created_at":"Fri, 25 Feb 2011 17:41:25 +0000","from_user":"NinaMaechik","id_str":"41191047853912064","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Glad I can remember my twitter password!  LOL!  Hugs to Nick from his aunties...","id":41191047853912064,"from_user_id":229024950,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"215545822","profile_image_url":"http://a2.twimg.com/profile_images/1239909540/RTL_normal.png","created_at":"Fri, 25 Feb 2011 17:41:25 +0000","from_user":"RightToLaugh","id_str":"41191047371558912","metadata":{"result_type":"recent"},"to_user_id":null,"text":"#Bacon wrapped dates http://bit.ly/hUxVC9 Just like my grandma used to make http://twitter.com/#","id":41191047371558912,"from_user_id":215545822,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"217845526","profile_image_url":"http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png","created_at":"Fri, 25 Feb 2011 17:41:25 +0000","from_user":"TheShoeLooker","id_str":"41191047052804096","metadata":{"result_type":"recent"},"to_user_id":38033240,"text":"@ShoeDazzle love love love to twitter or blog about your deals and shoes!\ntheshoelooker.blogspot.com","id":41191047052804096,"from_user_id":217845526,"to_user":"shoedazzle","geo":null,"iso_language_code":"en","to_user_id_str":"38033240","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"24953438","profile_image_url":"http://a3.twimg.com/profile_images/1181252763/76820_10150100014748783_677998782_7386695_4377006_n_normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:25 +0000","from_user":"liljermaine32","id_str":"41191046025056256","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Twitter fam I gotta question 4 ya is texas south or midwest #arguments","id":41191046025056256,"from_user_id":24953438,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://mobile.twitter.com&quot; rel=&quot;nofollow&quot;&gt;Twitter for Android&lt;/a&gt;"},{"from_user_id_str":"122451024","profile_image_url":"http://a2.twimg.com/profile_images/1143400129/100706-ETCanada_201134_1__normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:25 +0000","from_user":"Kim_DEon","id_str":"41191045161164801","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Help @CARE @EvaLongoria and ME use Twitter to change the lives of girls in poverty across the world! Ready? Go to http://TwitChange.com NOW!","id":41191045161164801,"from_user_id":122451024,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"128351479","profile_image_url":"http://a3.twimg.com/profile_images/1150198829/Kirby_Photo__edit__normal.JPG","created_at":"Fri, 25 Feb 2011 17:41:24 +0000","from_user":"TheDaveKirby","id_str":"41191043894493184","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Maybe its time to clean house...MY HOUSE http://bit.ly/ejG22P","id":41191043894493184,"from_user_id":128351479,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"87512773","profile_image_url":"http://a0.twimg.com/profile_images/1252511106/image_normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:24 +0000","from_user":"supportthehood","id_str":"41191043206610946","metadata":{"result_type":"recent"},"to_user_id":2467330,"text":"@eastcoastmp3 #FF  it's the way twitter works !!!","id":41191043206610946,"from_user_id":87512773,"to_user":"eastcoastmp3","geo":null,"iso_language_code":"en","to_user_id_str":"2467330","source":"&lt;a href=&quot;http://twitter.com/&quot; rel=&quot;nofollow&quot;&gt;Twitter for iPhone&lt;/a&gt;"},{"from_user_id_str":"149894545","profile_image_url":"http://a0.twimg.com/profile_images/1219334400/patrick_camo_normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:24 +0000","from_user":"suburbpat","id_str":"41191040912330752","metadata":{"result_type":"recent"},"to_user_id":null,"text":"I havent had a rib session on twitter in a while.......I wanna Rib with a nigga with alot followers lol","id":41191040912330752,"from_user_id":149894545,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"97749622","profile_image_url":"http://a0.twimg.com/profile_images/1254640781/IMG1633A_normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:24 +0000","from_user":"jrricardosantos","id_str":"41191040547430400","metadata":{"result_type":"recent"},"to_user_id":101017476,"text":"@yofzs pq tirou o CAPS LOCK? kkkk tava bem legal...hehehe eu estou no t\u00e9dio...kkk tbm estou no twitter e msn..aff!!","id":41191040547430400,"from_user_id":97749622,"to_user":"yofzs","geo":null,"iso_language_code":"en","to_user_id_str":"101017476","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"94381536","profile_image_url":"http://a1.twimg.com/profile_images/1174777929/41509_100001699300415_3521888_n_normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:23 +0000","from_user":"DonNieves","id_str":"41191038978752512","metadata":{"result_type":"recent"},"to_user_id":151926400,"text":"@TiiH13 cheira meu ovo, no email, no orkut, no msn, no facebook, no twitter, no skype, no spark, no ICQ e no google talk","id":41191038978752512,"from_user_id":94381536,"to_user":"TiiH13","geo":null,"iso_language_code":"en","to_user_id_str":"151926400","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"211858432","profile_image_url":"http://a3.twimg.com/profile_images/1231906180/JonasBrothers_normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:23 +0000","from_user":"OriginalJBfans","id_str":"41191038328651776","metadata":{"result_type":"recent"},"to_user_id":null,"text":"I literally forgot about this twitter... so whats occuring followers?","id":41191038328651776,"from_user_id":211858432,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"90317132","profile_image_url":"http://a3.twimg.com/profile_images/1230085056/254101635_normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:23 +0000","from_user":"Just2smooth","id_str":"41191036936126464","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Wassup twitter","id":41191036936126464,"from_user_id":90317132,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://ubersocial.com&quot; rel=&quot;nofollow&quot;&gt;\u00dcberSocial&lt;/a&gt;"},{"from_user_id_str":"144771504","profile_image_url":"http://a1.twimg.com/profile_images/1243343836/image_normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:23 +0000","from_user":"ima_b_b_badman","id_str":"41191036638339072","metadata":{"result_type":"recent"},"to_user_id":null,"text":"i been M.I.A. all day twitter my bad","id":41191036638339072,"from_user_id":144771504,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"207922664","profile_image_url":"http://a2.twimg.com/profile_images/1237106108/258356167_normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:22 +0000","from_user":"itsthecarter_","id_str":"41191036013391873","metadata":{"result_type":"recent"},"to_user_id":null,"text":"twitter is a stupid addiction.","id":41191036013391873,"from_user_id":207922664,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://ubersocial.com&quot; rel=&quot;nofollow&quot;&gt;\u00dcberSocial&lt;/a&gt;"},{"from_user_id_str":"150000649","profile_image_url":"http://a0.twimg.com/profile_images/1237789059/belllaa_normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:22 +0000","from_user":"angelicabrvo","id_str":"41191035707207680","metadata":{"result_type":"recent"},"to_user_id":null,"text":"que hubo twitter? huy que feoxd","id":41191035707207680,"from_user_id":150000649,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://blackberry.com/twitter&quot; rel=&quot;nofollow&quot;&gt;Twitter for BlackBerry\u00ae&lt;/a&gt;"},{"from_user_id_str":"185713003","profile_image_url":"http://a2.twimg.com/profile_images/1191421540/163229_177015295661467_154687431227587_492170_2147500_n_normal.jpg","created_at":"Fri, 25 Feb 2011 17:41:22 +0000","from_user":"downbytheshores","id_str":"41191033945595905","metadata":{"result_type":"recent"},"to_user_id":null,"text":"follow us on twitter\nwww.twitter.com/downbytheshores http://fb.me/DhDS915e","id":41191033945595905,"from_user_id":185713003,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.facebook.com/twitter&quot; rel=&quot;nofollow&quot;&gt;Facebook&lt;/a&gt;"}],"max_id":41191052790603776,"since_id":38643906774044672,"refresh_url":"?since_id=41191052790603776&q=twitter","next_page":"?page=2&max_id=41191052790603776&rpp=20&lang=en&q=twitter","results_per_page":20,"page":1,"completed_in":0.128719,"warning":"adjusted since_id to 38643906774044672 (), requested since_id was older than allowed -- since_id removed for pagination.","since_id_str":"38643906774044672","max_id_str":"41191052790603776","query":"twitter"}
+ test/json-data/twitter50.json view
@@ -0,0 +1,1 @@+{"results":[{"from_user_id_str":"207858021","profile_image_url":"http://a3.twimg.com/sticky/default_profile_images/default_profile_2_normal.png","created_at":"Wed, 26 Jan 2011 04:30:38 +0000","from_user":"pboudarga","id_str":"30120402839666689","metadata":{"result_type":"recent"},"to_user_id":null,"text":"I'm at Rolla Sushi Grill (27737 Bouquet Canyon Road, #106, Btw Haskell Canyon and Rosedell Drive, Saugus) http://4sq.com/gqqdhs","id":30120402839666689,"from_user_id":207858021,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://foursquare.com&quot; rel=&quot;nofollow&quot;&gt;foursquare&lt;/a&gt;"},{"from_user_id_str":"69988683","profile_image_url":"http://a0.twimg.com/profile_images/1211955817/avatar_7888_normal.gif","created_at":"Wed, 26 Jan 2011 04:25:23 +0000","from_user":"YNK33","id_str":"30119083059978240","metadata":{"result_type":"recent"},"to_user_id":null,"text":"hsndfile 0.5.0: Free and open source Haskell bindings for libsndfile http://bit.ly/gHaBWG Mac Os","id":30119083059978240,"from_user_id":69988683,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"81492","profile_image_url":"http://a1.twimg.com/profile_images/423894208/Picture_7_normal.jpg","created_at":"Wed, 26 Jan 2011 04:24:28 +0000","from_user":"satzz","id_str":"30118851488251904","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Emacs\u306e\u30e2\u30fc\u30c9\u8868\u793a\u304c\u4eca(Ruby Controller Outputz RoR Flymake REl hs)\u3068\u306a\u3063\u3066\u3066\u3088\u304f\u308f\u304b\u3089\u306a\u3044\u3093\u3060\u3051\u3069\u6700\u5f8c\u306eREl\u3068\u304bhs\u3063\u3066\u4f55\u3060\u308d\u3046\u2026haskell\u3068\u304b2\u5e74\u4ee5\u4e0a\u66f8\u3044\u3066\u306a\u3044\u3051\u3069\u2026","id":30118851488251904,"from_user_id":81492,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;"},{"from_user_id_str":"9518356","profile_image_url":"http://a2.twimg.com/profile_images/119165723/ocaml-icon_normal.png","created_at":"Wed, 26 Jan 2011 04:19:19 +0000","from_user":"planet_ocaml","id_str":"30117557788741632","metadata":{"result_type":"recent"},"to_user_id":null,"text":"I so miss #haskell type classes in #ocaml - i want to do something like refinement. Also why does ocaml not have... http://bit.ly/geYRwt","id":30117557788741632,"from_user_id":9518356,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"218059","profile_image_url":"http://a1.twimg.com/profile_images/1053837723/twitter-icon9_normal.jpg","created_at":"Wed, 26 Jan 2011 04:16:32 +0000","from_user":"aprikip","id_str":"30116854940835840","metadata":{"result_type":"recent"},"to_user_id":null,"text":"yatex-mode\u3084haskell-mode\u306e\u3053\u3068\u3067\u3059\u306d\u3001\u308f\u304b\u308a\u307e\u3059\u3002","id":30116854940835840,"from_user_id":218059,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://sites.google.com/site/yorufukurou/&quot; rel=&quot;nofollow&quot;&gt;YoruFukurou&lt;/a&gt;"},{"from_user_id_str":"216363","profile_image_url":"http://a1.twimg.com/profile_images/72454310/Tim-Avatar_normal.png","created_at":"Wed, 26 Jan 2011 04:15:30 +0000","from_user":"dysinger","id_str":"30116594684264448","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Haskell in Hawaii tonight for me... #fun","id":30116594684264448,"from_user_id":216363,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.nambu.com/&quot; rel=&quot;nofollow&quot;&gt;Nambu&lt;/a&gt;"},{"from_user_id_str":"1774820","profile_image_url":"http://a2.twimg.com/profile_images/61169291/dan_desert_thumb_normal.jpg","created_at":"Wed, 26 Jan 2011 04:13:36 +0000","from_user":"DanMil","id_str":"30116117682851840","metadata":{"result_type":"recent"},"to_user_id":1594784,"text":"@ojrac @chewedwire @tomheon Haskell isn't a language, it's a belief system.  A seductive one...","id":30116117682851840,"from_user_id":1774820,"to_user":"ojrac","geo":null,"iso_language_code":"en","to_user_id_str":"1594784","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"659256","profile_image_url":"http://a0.twimg.com/profile_images/746976711/angular-final_normal.jpg","created_at":"Wed, 26 Jan 2011 04:11:06 +0000","from_user":"djspiewak","id_str":"30115488931520512","metadata":{"result_type":"recent"},"to_user_id":null,"text":"One of the very nice things about Haskell as opposed to SML is the reduced proliferation of identifiers (e.g. andb, orb, etc). #typeclasses","id":30115488931520512,"from_user_id":659256,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://itunes.apple.com/us/app/twitter/id409789998?mt=12&quot; rel=&quot;nofollow&quot;&gt;Twitter for Mac&lt;/a&gt;"},{"from_user_id_str":"144546280","profile_image_url":"http://a1.twimg.com/a/1295051201/images/default_profile_1_normal.png","created_at":"Wed, 26 Jan 2011 04:06:12 +0000","from_user":"listwarenet","id_str":"30114255890026496","metadata":{"result_type":"recent"},"to_user_id":null,"text":"http://www.listware.net/201101/haskell-cafe/84752-re-haskell-cafe-gpl-license-of-h-matrix-and-prelude-numeric.html Re: Haskell-c","id":30114255890026496,"from_user_id":144546280,"geo":null,"iso_language_code":"no","to_user_id_str":null,"source":"&lt;a href=&quot;http://1e10.org/cloud/&quot; rel=&quot;nofollow&quot;&gt;1e10&lt;/a&gt;"},{"from_user_id_str":"1594784","profile_image_url":"http://a2.twimg.com/profile_images/378515773/square-profile_normal.jpg","created_at":"Wed, 26 Jan 2011 04:01:29 +0000","from_user":"ojrac","id_str":"30113067333324800","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @tomheon: @ojrac @chewedwire Don't worry, learning Haskell will not give you any clear idea what monad means.","id":30113067333324800,"from_user_id":1594784,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"207589736","profile_image_url":"http://a3.twimg.com/profile_images/1225527428/headshot_1_normal.jpg","created_at":"Wed, 26 Jan 2011 04:00:13 +0000","from_user":"ashleevelazq101","id_str":"30112747555397632","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Federal investigation finds safety violations at The Acadia Hospital: By Meg Haskell, BDN Staff The investigatio... http://bit.ly/dONnpn","id":30112747555397632,"from_user_id":207589736,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"17671137","profile_image_url":"http://a2.twimg.com/profile_images/290264834/Haskell-logo-outer-glow_normal.png","created_at":"Wed, 26 Jan 2011 03:58:00 +0000","from_user":"Hackage","id_str":"30112192346984448","metadata":{"result_type":"recent"},"to_user_id":null,"text":"streams 0.4, added by EdwardKmett: Various Haskell 2010 stream comonads http://bit.ly/idBkPe","id":30112192346984448,"from_user_id":17671137,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"17489366","profile_image_url":"http://a3.twimg.com/sticky/default_profile_images/default_profile_2_normal.png","created_at":"Wed, 26 Jan 2011 03:58:00 +0000","from_user":"aapnoot","id_str":"30112191881420800","metadata":{"result_type":"recent"},"to_user_id":null,"text":"streams 0.4, added by EdwardKmett: Various Haskell 2010 stream comonads http://bit.ly/idBkPe","id":30112191881420800,"from_user_id":17489366,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"8530482","profile_image_url":"http://a2.twimg.com/profile_images/137867266/n608671563_7396_normal.jpg","created_at":"Wed, 26 Jan 2011 03:50:12 +0000","from_user":"jeffmclamb","id_str":"30110229207187456","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Angel - daemon to run and monitor processes like daemontools or god, written in Haskell http://ff.im/-wNyLk","id":30110229207187456,"from_user_id":8530482,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://friendfeed.com&quot; rel=&quot;nofollow&quot;&gt;FriendFeed&lt;/a&gt;"},{"from_user_id_str":"177539201","profile_image_url":"http://a0.twimg.com/profile_images/1178368800/img_normal.jpeg","created_at":"Wed, 26 Jan 2011 03:46:01 +0000","from_user":"tomheon","id_str":"30109174645919744","metadata":{"result_type":"recent"},"to_user_id":1594784,"text":"@ojrac @chewedwire Don't worry, learning Haskell will not give you any clear idea what monad means.","id":30109174645919744,"from_user_id":177539201,"to_user":"ojrac","geo":null,"iso_language_code":"en","to_user_id_str":"1594784","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"1594784","profile_image_url":"http://a2.twimg.com/profile_images/378515773/square-profile_normal.jpg","created_at":"Wed, 26 Jan 2011 03:44:34 +0000","from_user":"ojrac","id_str":"30108808684503040","metadata":{"result_type":"recent"},"to_user_id":128028225,"text":"@chewedwire @tomheon Why are you making me curious about Haskell? I LIKE not knowing what monad means!!","id":30108808684503040,"from_user_id":1594784,"to_user":"chewedwire","geo":null,"iso_language_code":"en","to_user_id_str":"128028225","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"23750094","profile_image_url":"http://a0.twimg.com/profile_images/951373780/Moeinthecar_normal.jpg","created_at":"Wed, 26 Jan 2011 03:41:54 +0000","from_user":"shokalshab","id_str":"30108140443795456","metadata":{"result_type":"recent"},"to_user_id":null,"text":"I'm at Magnitude Cheer @ Gymnastics Olympica USA (7735 Haskell Ave., btw Saticoy &amp; Strathern, Van Nuys) http://4sq.com/gmXfaL","id":30108140443795456,"from_user_id":23750094,"geo":null,"iso_language_code":"en","place":{"id":"4e4a2a2f86cb2946","type_":"poi","full_name":"Gymnastics Olympica USA, Van Nuys"},"to_user_id_str":null,"source":"&lt;a href=&quot;http://foursquare.com&quot; rel=&quot;nofollow&quot;&gt;foursquare&lt;/a&gt;"},{"from_user_id_str":"8135112","profile_image_url":"http://a0.twimg.com/profile_images/1165240350/LIMITED_normal.jpg","created_at":"Wed, 26 Jan 2011 03:41:35 +0000","from_user":"Claricei","id_str":"30108059208515584","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @kristenmchugh22: @KeithOlbermann Ryan looks like Jughead w/o the hat. And sounds as mealy-mouthed as Eddie Haskell.","id":30108059208515584,"from_user_id":8135112,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"128028225","profile_image_url":"http://a1.twimg.com/profile_images/1224994593/Coding_Drunk_normal.jpg","created_at":"Wed, 26 Jan 2011 03:40:02 +0000","from_user":"chewedwire","id_str":"30107670367182848","metadata":{"result_type":"recent"},"to_user_id":177539201,"text":"@tomheon Cool, I'll take a look. I feel like I should mention this: http://bit.ly/hVstDM","id":30107670367182848,"from_user_id":128028225,"to_user":"tomheon","geo":null,"iso_language_code":"en","to_user_id_str":"177539201","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"8679778","profile_image_url":"http://a3.twimg.com/profile_images/1195318056/me_nyc_12_18_10_icon_normal.jpg","created_at":"Wed, 26 Jan 2011 03:38:42 +0000","from_user":"kristenmchugh22","id_str":"30107332381777920","metadata":{"result_type":"recent"},"to_user_id":756269,"text":"@KeithOlbermann Ryan looks like Jughead w/o the hat. And sounds as mealy-mouthed as Eddie Haskell.","id":30107332381777920,"from_user_id":8679778,"to_user":"KeithOlbermann","geo":null,"iso_language_code":"en","to_user_id_str":"756269","source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"103316559","profile_image_url":"http://a1.twimg.com/profile_images/1179458751/bc3beab4-d59d-4e78-b13b-50747986cfa2_normal.png","created_at":"Wed, 26 Jan 2011 03:36:15 +0000","from_user":"cityslikr","id_str":"30106719153557504","metadata":{"result_type":"recent"},"to_user_id":null,"text":"&quot;Social safety net into a hammock.&quot; So says Eddie Haskell with the GOP response. #SOTU","id":30106719153557504,"from_user_id":103316559,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://blackberry.com/twitter&quot; rel=&quot;nofollow&quot;&gt;Twitter for BlackBerry\u00ae&lt;/a&gt;"},{"from_user_id_str":"169063143","profile_image_url":"http://a0.twimg.com/profile_images/1160506212/9697_1_normal.gif","created_at":"Wed, 26 Jan 2011 03:31:52 +0000","from_user":"wrkforce_safety","id_str":"30105614919143424","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Federal investigation finds safety violations at The Acadia Hospital: By Meg Haskell, BDN Staff The investigatio... http://bit.ly/gA60C1","id":30105614919143424,"from_user_id":169063143,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"177539201","profile_image_url":"http://a0.twimg.com/profile_images/1178368800/img_normal.jpeg","created_at":"Wed, 26 Jan 2011 03:29:40 +0000","from_user":"tomheon","id_str":"30105060960632832","metadata":{"result_type":"recent"},"to_user_id":128028225,"text":"@chewedwire Great book on Haskell: http://oreilly.com/catalog/9780596514983","id":30105060960632832,"from_user_id":177539201,"to_user":"chewedwire","geo":null,"iso_language_code":"en","to_user_id_str":"128028225","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"782692","profile_image_url":"http://a0.twimg.com/profile_images/1031304589/Profile.2007.1_normal.jpg","created_at":"Wed, 26 Jan 2011 03:29:19 +0000","from_user":"turnageb","id_str":"30104974591533057","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @ovillalon: Paul Ryan solves the mystery of whatever happened to Eddie Haskell. #sotu","id":30104974591533057,"from_user_id":782692,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"128028225","profile_image_url":"http://a1.twimg.com/profile_images/1224994593/Coding_Drunk_normal.jpg","created_at":"Wed, 26 Jan 2011 03:28:04 +0000","from_user":"chewedwire","id_str":"30104657267265536","metadata":{"result_type":"recent"},"to_user_id":177539201,"text":"@tomheon I always loved the pattern matching in SML and it looks like Haskell is MUCH better at it. I'm messing around now at tryhaskell.org","id":30104657267265536,"from_user_id":128028225,"to_user":"tomheon","geo":null,"iso_language_code":"en","to_user_id_str":"177539201","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"12834082","profile_image_url":"http://a3.twimg.com/profile_images/1083036140/mugshot_normal.png","created_at":"Wed, 26 Jan 2011 03:28:01 +0000","from_user":"ovillalon","id_str":"30104647213518848","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Paul Ryan solves the mystery of whatever happened to Eddie Haskell. #sotu","id":30104647213518848,"from_user_id":12834082,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"13540930","profile_image_url":"http://a1.twimg.com/profile_images/1205312219/23467_350321383101_821143101_5114147_3010041_n_normal.jpg","created_at":"Wed, 26 Jan 2011 03:26:02 +0000","from_user":"goodfox","id_str":"30104146455560192","metadata":{"result_type":"recent"},"to_user_id":10226179,"text":"@billykeene22 Bordeaux is one of my heroes. I was so excited when he accepted the invitation to campus. He's been a great friend to Haskell.","id":30104146455560192,"from_user_id":13540930,"to_user":"billykeene22","geo":null,"iso_language_code":"en","to_user_id_str":"10226179","source":"&lt;a href=&quot;http://www.ubertwitter.com/bb/download.php&quot; rel=&quot;nofollow&quot;&gt;\u00dcberTwitter&lt;/a&gt;"},{"from_user_id_str":"18616016","profile_image_url":"http://a2.twimg.com/profile_images/1173522726/214397343_normal.jpg","created_at":"Wed, 26 Jan 2011 03:25:18 +0000","from_user":"josej30","id_str":"30103962313031681","metadata":{"result_type":"recent"},"to_user_id":14870909,"text":"@cris7ian Ahh bueno multiparadigma ya es respetable :) Empezar\u00e9 a explotar la parte funcional de los lenguajes ahora #Haskell","id":30103962313031681,"from_user_id":18616016,"to_user":"Cris7ian","geo":null,"iso_language_code":"es","to_user_id_str":"14870909","source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"14870909","profile_image_url":"http://a2.twimg.com/profile_images/1176930429/oso_yo_normal.png","created_at":"Wed, 26 Jan 2011 03:23:43 +0000","from_user":"Cris7ian","id_str":"30103562360983553","metadata":{"result_type":"recent"},"to_user_id":18616016,"text":"@josej30 hahaha no, es multiparadigma y es bastante lazy. Nothing like haskell, pero s\u00ed, el de Flash","id":30103562360983553,"from_user_id":14870909,"to_user":"josej30","geo":null,"iso_language_code":"es","to_user_id_str":"18616016","source":"&lt;a href=&quot;http://www.echofon.com/&quot; rel=&quot;nofollow&quot;&gt;Echofon&lt;/a&gt;"},{"from_user_id_str":"2421643","profile_image_url":"http://a0.twimg.com/profile_images/1190361665/ernestgrumbles-17_normal.jpg","created_at":"Wed, 26 Jan 2011 03:20:24 +0000","from_user":"ernestgrumbles","id_str":"30102730756333568","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Wow... WolframAlpha did not know who Eddie Haskell is.  Guess I'll never use that &quot;knowledge engine&quot; again.","id":30102730756333568,"from_user_id":2421643,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"128028225","profile_image_url":"http://a1.twimg.com/profile_images/1224994593/Coding_Drunk_normal.jpg","created_at":"Wed, 26 Jan 2011 03:14:21 +0000","from_user":"chewedwire","id_str":"30101204428132352","metadata":{"result_type":"recent"},"to_user_id":177539201,"text":"@tomheon How is Haskell better/different from CL or Scheme? I honestly don't know, although I'm becoming more curious.","id":30101204428132352,"from_user_id":128028225,"to_user":"tomheon","geo":null,"iso_language_code":"en","to_user_id_str":"177539201","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"13540930","profile_image_url":"http://a1.twimg.com/profile_images/1205312219/23467_350321383101_821143101_5114147_3010041_n_normal.jpg","created_at":"Wed, 26 Jan 2011 03:06:54 +0000","from_user":"goodfox","id_str":"30099329809129473","metadata":{"result_type":"recent"},"to_user_id":null,"text":"A day of vision &amp; speeches. #SOTU now. And a wonderful Haskell Convocation address earlier today by Sinte Gleske President Lionel Bordeaux.","id":30099329809129473,"from_user_id":13540930,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.ubertwitter.com/bb/download.php&quot; rel=&quot;nofollow&quot;&gt;\u00dcberTwitter&lt;/a&gt;"},{"from_user_id_str":"119185220","profile_image_url":"http://a0.twimg.com/profile_images/1089027228/dfg_normal.jpg","created_at":"Wed, 26 Jan 2011 03:03:38 +0000","from_user":"LaLiciouz_03","id_str":"30098510623805440","metadata":{"result_type":"recent"},"to_user_id":null,"text":"The Game with the girl room 330 Haskell follow us...","id":30098510623805440,"from_user_id":119185220,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"18616016","profile_image_url":"http://a2.twimg.com/profile_images/1173522726/214397343_normal.jpg","created_at":"Wed, 26 Jan 2011 02:57:25 +0000","from_user":"josej30","id_str":"30096946144215040","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Voy a extra\u00f1ar Haskell cuando regrese al mundo imperativo. Hay alg\u00fan lenguaje imperativo que tenga este poder funcional? #ci3661","id":30096946144215040,"from_user_id":18616016,"geo":null,"iso_language_code":"es","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"17671137","profile_image_url":"http://a2.twimg.com/profile_images/290264834/Haskell-logo-outer-glow_normal.png","created_at":"Wed, 26 Jan 2011 02:55:32 +0000","from_user":"Hackage","id_str":"30096471814574080","metadata":{"result_type":"recent"},"to_user_id":null,"text":"comonad-transformers 0.9.0, added by EdwardKmett: Haskell 98 comonad transformers http://bit.ly/h6xIsf","id":30096471814574080,"from_user_id":17671137,"geo":null,"iso_language_code":"no","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"},{"from_user_id_str":"177539201","profile_image_url":"http://a0.twimg.com/profile_images/1178368800/img_normal.jpeg","created_at":"Wed, 26 Jan 2011 02:48:12 +0000","from_user":"tomheon","id_str":"30094626920603649","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Every time I look at Haskell I love it more.","id":30094626920603649,"from_user_id":177539201,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"60792568","profile_image_url":"http://a0.twimg.com/profile_images/1203647517/glenda-flash_normal.jpg","created_at":"Wed, 26 Jan 2011 02:40:42 +0000","from_user":"r_takaishi","id_str":"30092735935422464","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Haskell\u3088\u308aD\u8a00\u8a9e\u304c\u4e0a\u3068\u306f\u601d\u308f\u306a\u304b\u3063\u305f\uff0e http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html","id":30092735935422464,"from_user_id":60792568,"geo":null,"iso_language_code":"ja","to_user_id_str":null,"source":"&lt;a href=&quot;http://twmode.sf.net/&quot; rel=&quot;nofollow&quot;&gt;twmode&lt;/a&gt;"},{"from_user_id_str":"160145510","profile_image_url":"http://a0.twimg.com/profile_images/1218108166/going_galt_normal.jpg","created_at":"Wed, 26 Jan 2011 02:39:31 +0000","from_user":"wtp1787","id_str":"30092439653974018","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @KLSouth: Eddie Haskell Goes to Washington...  &quot;You look really nice tonight, Ms Cleaver&quot;","id":30092439653974018,"from_user_id":160145510,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"96616016","profile_image_url":"http://a1.twimg.com/profile_images/1196978169/Picture0002_normal.jpg","created_at":"Wed, 26 Jan 2011 02:39:20 +0000","from_user":"MelissaRNMBA","id_str":"30092392203816960","metadata":{"result_type":"recent"},"to_user_id":14862975,"text":"@KLSouth At least Eddie Haskell was entertaining.","id":30092392203816960,"from_user_id":96616016,"to_user":"KLSouth","geo":null,"iso_language_code":"en","to_user_id_str":"14862975","source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"14862975","profile_image_url":"http://a0.twimg.com/profile_images/421596393/kls_4_normal.JPG","created_at":"Wed, 26 Jan 2011 02:38:29 +0000","from_user":"KLSouth","id_str":"30092178327871489","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Eddie Haskell Goes to Washington...  &quot;You look really nice tonight, Ms Cleaver&quot;","id":30092178327871489,"from_user_id":14862975,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.tweetdeck.com&quot; rel=&quot;nofollow&quot;&gt;TweetDeck&lt;/a&gt;"},{"from_user_id_str":"144546280","profile_image_url":"http://a1.twimg.com/a/1295051201/images/default_profile_1_normal.png","created_at":"Wed, 26 Jan 2011 02:36:17 +0000","from_user":"listwarenet","id_str":"30091626869161984","metadata":{"result_type":"recent"},"to_user_id":null,"text":"http://www.listware.net/201101/haskell-beginners/84641-haskell-beginners-wildcards-in-expressions.html Haskell-beginners -  Wild","id":30091626869161984,"from_user_id":144546280,"geo":null,"iso_language_code":"no","to_user_id_str":null,"source":"&lt;a href=&quot;http://1e10.org/cloud/&quot; rel=&quot;nofollow&quot;&gt;1e10&lt;/a&gt;"},{"from_user_id_str":"24538048","profile_image_url":"http://a2.twimg.com/profile_images/1117267605/rope_normal.jpg","created_at":"Wed, 26 Jan 2011 02:23:14 +0000","from_user":"dbph","id_str":"30088341030440960","metadata":{"result_type":"recent"},"to_user_id":null,"text":"RT @dnene: Skilled Calisthenics. Haskell code that outputs python which spits ruby which emits the haskell source. http://j.mp/YlQUL via @mfeathers","id":30088341030440960,"from_user_id":24538048,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"},{"from_user_id_str":"158951567","profile_image_url":"http://a3.twimg.com/profile_images/962721419/Logo_3_normal.jpg","created_at":"Wed, 26 Jan 2011 01:58:31 +0000","from_user":"YubaVetTech","id_str":"30082124207886336","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Dr. Haskell has just been awarded the prestigious Hayward Award for \u2018Excellence in Education\u2019. This award honors... http://fb.me/QgQCxd74","id":30082124207886336,"from_user_id":158951567,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.facebook.com/twitter&quot; rel=&quot;nofollow&quot;&gt;Facebook&lt;/a&gt;"},{"from_user_id_str":"158951567","profile_image_url":"http://a3.twimg.com/profile_images/962721419/Logo_3_normal.jpg","created_at":"Wed, 26 Jan 2011 01:56:04 +0000","from_user":"YubaVetTech","id_str":"30081505476743168","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Dr. Haskell has just been awarded the prestigous Haward Award for Excellence in Education. This award honors... http://fb.me/PC9mYmCR","id":30081505476743168,"from_user_id":158951567,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://www.facebook.com/twitter&quot; rel=&quot;nofollow&quot;&gt;Facebook&lt;/a&gt;"},{"from_user_id_str":"2331498","profile_image_url":"http://a3.twimg.com/profile_images/494632120/me_normal.jpg","created_at":"Wed, 26 Jan 2011 01:43:50 +0000","from_user":"Verus","id_str":"30078427155406848","metadata":{"result_type":"recent"},"to_user_id":79273052,"text":"@kami_joe \u3044\u3084\uff0c\u8ab2\u984c\u306f\u89e3\u6c7a\u5bfe\u8c61(\u30d1\u30ba\u30eb\u3068\u304b)\u3092\u4e0e\u3048\u3089\u308c\u3066\uff0c\u554f\u984c\u5b9a\u7fa9\u3068\u30d7\u30ed\u30b0\u30e9\u30df\u30f3\u30b0(\u6307\u5b9a\u8a00\u8a9e\u306fC++\u3082\u3057\u304f\u306fJava)\u3068\u3044\u3046\u3044\u308f\u3070\u666e\u901a\u306a\u8ab2\u984c\u3067\u306f\u3042\u308b\u3093\u3060\u3051\u3069\uff0e\u95a2\u6570\u578b\u8a00\u8a9e\u306fHaskell\u306e\u6388\u696d\u304c\u307e\u305f\u5225\u306b\u3042\u308b\u306e\uff0e","id":30078427155406848,"from_user_id":2331498,"to_user":"kami_joe","geo":null,"iso_language_code":"ja","to_user_id_str":"79273052","source":"&lt;a href=&quot;http://itunes.apple.com/us/app/twitter/id409789998?mt=12&quot; rel=&quot;nofollow&quot;&gt;Twitter for Mac&lt;/a&gt;"},{"from_user_id_str":"79151233","profile_image_url":"http://a2.twimg.com/a/1295051201/images/default_profile_2_normal.png","created_at":"Wed, 26 Jan 2011 01:40:37 +0000","from_user":"cz_newdrafts","id_str":"30077617361125376","metadata":{"result_type":"recent"},"to_user_id":null,"text":"Haskell programming language http://bit.ly/gpPAwB","id":30077617361125376,"from_user_id":79151233,"geo":null,"iso_language_code":"no","to_user_id_str":null,"source":"&lt;a href=&quot;http://tommorris.org/&quot; rel=&quot;nofollow&quot;&gt;tommorris' hacksample&lt;/a&gt;"},{"from_user_id_str":"2331498","profile_image_url":"http://a3.twimg.com/profile_images/494632120/me_normal.jpg","created_at":"Wed, 26 Jan 2011 01:02:57 +0000","from_user":"Verus","id_str":"30068139416879104","metadata":{"result_type":"recent"},"to_user_id":9252720,"text":"@shukukei Java\u3068C\u306f\u3042\u308b\u7a0b\u5ea6\u66f8\u3051\u3066\u3042\u305f\u308a\u307e\u3048\u306a\u3068\u3053\u308d\u304c\u3042\u308b\u304b\u3089\u306a\u30fc\uff0ePython\u306f\u500b\u4eba\u7684\u306b\u611f\u899a\u304c\u5408\u308f\u306a\u3044\uff0e\u611f\u899a\u306a\u306e\u3067\uff0c\u3082\u3046\u3069\u3046\u3057\u3088\u3046\u3082\u306a\u3044\uff57 \u3044\u307e\u306fScala\u3068Haskell\u3092\u3082\u3063\u3068\u6975\u3081\u305f\u3044\u3068\u3053\u308d\uff0e","id":30068139416879104,"from_user_id":2331498,"to_user":"shukukei","geo":null,"iso_language_code":"ja","to_user_id_str":"9252720","source":"&lt;a href=&quot;http://itunes.apple.com/us/app/twitter/id409789998?mt=12&quot; rel=&quot;nofollow&quot;&gt;Twitter for Mac&lt;/a&gt;"},{"from_user_id_str":"2781460","profile_image_url":"http://a0.twimg.com/profile_images/82526625/.joeyicon_normal.jpg","created_at":"Wed, 26 Jan 2011 01:02:35 +0000","from_user":"joeyhess","id_str":"30068046538219520","metadata":{"result_type":"recent"},"to_user_id":null,"text":"just figured out that I can use parameterized types to remove a dependency loop in git-annex's type definitions. whee #haskell","id":30068046538219520,"from_user_id":2781460,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://identi.ca&quot; rel=&quot;nofollow&quot;&gt;identica&lt;/a&gt;"},{"from_user_id_str":"144546280","profile_image_url":"http://a1.twimg.com/a/1295051201/images/default_profile_1_normal.png","created_at":"Wed, 26 Jan 2011 00:54:22 +0000","from_user":"listwarenet","id_str":"30065977920061440","metadata":{"result_type":"recent"},"to_user_id":null,"text":"http://www.listware.net/201101/haskell-beginners/84466-haskell-beginners-bytestring-question.html Haskell-beginners -  Bytestrin","id":30065977920061440,"from_user_id":144546280,"geo":null,"iso_language_code":"no","to_user_id_str":null,"source":"&lt;a href=&quot;http://1e10.org/cloud/&quot; rel=&quot;nofollow&quot;&gt;1e10&lt;/a&gt;"},{"from_user_id_str":"1291845","profile_image_url":"http://a0.twimg.com/profile_images/1225743404/ThinOxygen-small-opaque-solidarity_normal.png","created_at":"Wed, 26 Jan 2011 00:52:07 +0000","from_user":"_aaron_","id_str":"30065412242669568","metadata":{"result_type":"recent"},"to_user_id":null,"text":"wanted: librly licensed high level native compiled lang with min runtime (otherwise cobra/mono would be perfect) for win. lua? haskell? ooc?","id":30065412242669568,"from_user_id":1291845,"geo":null,"iso_language_code":"en","to_user_id_str":null,"source":"&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"}],"max_id":30120402839666689,"since_id":0,"refresh_url":"?since_id=30120402839666689&q=haskell","next_page":"?page=2&max_id=30120402839666689&rpp=50&q=haskell","results_per_page":50,"page":1,"completed_in":0.291696,"since_id_str":"0","max_id_str":"30120402839666689","query":"haskell"}
+ word24-bench/Benchmark.hs view
@@ -0,0 +1,32 @@+module Main where++import Criterion.Main+import Control.DeepSeq+import Data.Int.Int24+import Data.Word.Word24+import Data.Int+import Data.Word+import Data.List++main = defaultMain+  [ bgroup "Int24" intses+  , bgroup "Word24" wordses+  , bgroup "Int16"    baseses+  , bgroup "Word16" basessW+  ]++benches :: (Enum i, Num i, NFData i, Integral i) => i -> [Benchmark]+benches x =+   [ bench "Add" $ nf (\i -> foldl' (+) i [1..100]) x+   , bench "Mul" $ nf (\i -> foldl' (*) i [1..100]) x+   , bench "quot" $ nf (\i -> map (flip quot i) [1..25]) x+   , bench "rem" $ nf (\i -> map (flip rem i) [1..25]) x+   , bench "div" $ nf (\i -> map (flip div i) [1..25]) x+   , bench "mod" $ nf (\i -> map (flip mod i) [1..25]) x+   ]+{-# INLINE benches #-}++intses  = benches (1 :: Int24)+wordses = benches (1 :: Word24)+baseses = benches (1 :: Int16)+basessW = benches (1 :: Word16)